diff --git a/src/main/java/com/back/domain/notification/event/community/CommunityNotificationEventListener.java b/src/main/java/com/back/domain/notification/event/community/CommunityNotificationEventListener.java index 0ba86bbf..55680a15 100644 --- a/src/main/java/com/back/domain/notification/event/community/CommunityNotificationEventListener.java +++ b/src/main/java/com/back/domain/notification/event/community/CommunityNotificationEventListener.java @@ -11,9 +11,9 @@ import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; +@Slf4j @Component @RequiredArgsConstructor -@Slf4j public class CommunityNotificationEventListener { private final NotificationService notificationService; diff --git a/src/main/java/com/back/domain/notification/event/study/StudyNotificationEventListener.java b/src/main/java/com/back/domain/notification/event/study/StudyNotificationEventListener.java index ad838316..d2f84965 100644 --- a/src/main/java/com/back/domain/notification/event/study/StudyNotificationEventListener.java +++ b/src/main/java/com/back/domain/notification/event/study/StudyNotificationEventListener.java @@ -11,9 +11,9 @@ import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; +@Slf4j @Component @RequiredArgsConstructor -@Slf4j public class StudyNotificationEventListener { private final NotificationService notificationService; diff --git a/src/test/java/com/back/domain/board/service/CommentServiceTest.java b/src/test/java/com/back/domain/board/service/CommentServiceTest.java index 37a942eb..87e32a26 100644 --- a/src/test/java/com/back/domain/board/service/CommentServiceTest.java +++ b/src/test/java/com/back/domain/board/service/CommentServiceTest.java @@ -10,6 +10,7 @@ import com.back.domain.board.post.entity.Post; import com.back.domain.board.comment.repository.CommentRepository; import com.back.domain.board.post.repository.PostRepository; +import com.back.domain.notification.service.NotificationService; import com.back.domain.user.entity.User; import com.back.domain.user.entity.UserProfile; import com.back.domain.user.entity.UserStatus; @@ -24,6 +25,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.transaction.annotation.Transactional; import java.util.List; @@ -47,6 +49,9 @@ class CommentServiceTest { @Autowired private PostRepository postRepository; + @MockitoBean + private NotificationService notificationService; + // ====================== 댓글 생성 테스트 ====================== @Test @@ -496,4 +501,4 @@ void deleteReply_success() { boolean exists = commentRepository.findById(reply.getId()).isPresent(); assertThat(exists).isFalse(); } -} +} \ No newline at end of file diff --git a/src/test/java/com/back/domain/notification/event/community/CommunityNotificationEventListenerTest.java b/src/test/java/com/back/domain/notification/event/community/CommunityNotificationEventListenerTest.java new file mode 100644 index 00000000..633ea6d3 --- /dev/null +++ b/src/test/java/com/back/domain/notification/event/community/CommunityNotificationEventListenerTest.java @@ -0,0 +1,319 @@ +package com.back.domain.notification.event.community; + +import com.back.domain.notification.service.NotificationService; +import com.back.domain.user.entity.User; +import com.back.domain.user.repository.UserRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.*; + +@ExtendWith(MockitoExtension.class) +@DisplayName("CommunityNotificationEventListener 테스트") +class CommunityNotificationEventListenerTest { + + @Mock + private NotificationService notificationService; + + @Mock + private UserRepository userRepository; + + @InjectMocks + private CommunityNotificationEventListener listener; + + private User actor; + private User receiver; + + @BeforeEach + void setUp() { + actor = User.builder() + .id(1L) + .username("actor") + .email("actor@test.com") + .build(); + + receiver = User.builder() + .id(2L) + .username("receiver") + .email("receiver@test.com") + .build(); + } + + // ====================== 댓글 작성 이벤트 ====================== + + @Test + @DisplayName("댓글 작성 이벤트 수신 - 알림 생성 성공") + void t1() { + // given + CommentCreatedEvent event = new CommentCreatedEvent( + 1L, // actorId (댓글 작성자) + 2L, // receiverId (게시글 작성자) + 100L, // postId + 200L, // commentId + "댓글 내용" + ); + + given(userRepository.findById(1L)).willReturn(Optional.of(actor)); + given(userRepository.findById(2L)).willReturn(Optional.of(receiver)); + + // when + listener.handleCommentCreated(event); + + // then + verify(notificationService).createCommunityNotification( + eq(receiver), + eq(actor), + anyString(), // title + anyString(), // content + eq("/posts/100") + ); + } + + @Test + @DisplayName("댓글 작성 이벤트 - 작성자(actor) 없음") + void t2() { + // given + CommentCreatedEvent event = new CommentCreatedEvent( + 999L, // 존재하지 않는 actorId + 2L, + 100L, + 200L, + "댓글 내용" + ); + + given(userRepository.findById(999L)).willReturn(Optional.empty()); + + // when + listener.handleCommentCreated(event); + + // then + verify(notificationService, never()).createCommunityNotification( + any(), any(), anyString(), anyString(), anyString() + ); + } + + @Test + @DisplayName("댓글 작성 이벤트 - 수신자(receiver) 없음") + void t3() { + // given + CommentCreatedEvent event = new CommentCreatedEvent( + 1L, + 999L, // 존재하지 않는 receiverId + 100L, + 200L, + "댓글 내용" + ); + + given(userRepository.findById(1L)).willReturn(Optional.of(actor)); + given(userRepository.findById(999L)).willReturn(Optional.empty()); + + // when + listener.handleCommentCreated(event); + + // then + verify(notificationService, never()).createCommunityNotification( + any(), any(), anyString(), anyString(), anyString() + ); + } + + // ====================== 대댓글 작성 이벤트 ====================== + + @Test + @DisplayName("대댓글 작성 이벤트 수신 - 알림 생성 성공") + void t4() { + // given + ReplyCreatedEvent event = new ReplyCreatedEvent( + 1L, // actorId (대댓글 작성자) + 2L, // receiverId (댓글 작성자) + 100L, // postId + 200L, // parentCommentId + 300L, // replyId + "대댓글 내용" + ); + + given(userRepository.findById(1L)).willReturn(Optional.of(actor)); + given(userRepository.findById(2L)).willReturn(Optional.of(receiver)); + + // when + listener.handleReplyCreated(event); + + // then + verify(notificationService).createCommunityNotification( + eq(receiver), + eq(actor), + anyString(), + anyString(), + eq("/posts/100#comment-200") + ); + } + + @Test + @DisplayName("대댓글 작성 이벤트 - 작성자(actor) 없음") + void t5() { + // given + ReplyCreatedEvent event = new ReplyCreatedEvent( + 999L, // 존재하지 않는 actorId + 2L, + 100L, + 200L, + 300L, + "대댓글 내용" + ); + + given(userRepository.findById(999L)).willReturn(Optional.empty()); + + // when + listener.handleReplyCreated(event); + + // then + verify(notificationService, never()).createCommunityNotification( + any(), any(), anyString(), anyString(), anyString() + ); + } + + // ====================== 게시글 좋아요 이벤트 ====================== + + @Test + @DisplayName("게시글 좋아요 이벤트 수신 - 알림 생성 성공") + void t6() { + // given + PostLikedEvent event = new PostLikedEvent( + 1L, // actorId (좋아요 누른 사람) + 2L, // receiverId (게시글 작성자) + 100L, // postId + "게시글 제목" + ); + + given(userRepository.findById(1L)).willReturn(Optional.of(actor)); + given(userRepository.findById(2L)).willReturn(Optional.of(receiver)); + + // when + listener.handlePostLiked(event); + + // then + verify(notificationService).createCommunityNotification( + eq(receiver), + eq(actor), + anyString(), + anyString(), + eq("/posts/100") + ); + } + + @Test + @DisplayName("게시글 좋아요 이벤트 - 수신자 없음") + void t7() { + // given + PostLikedEvent event = new PostLikedEvent( + 1L, + 999L, // 존재하지 않는 receiverId + 100L, + "게시글 제목" + ); + + given(userRepository.findById(1L)).willReturn(Optional.of(actor)); + given(userRepository.findById(999L)).willReturn(Optional.empty()); + + // when + listener.handlePostLiked(event); + + // then + verify(notificationService, never()).createCommunityNotification( + any(), any(), anyString(), anyString(), anyString() + ); + } + + // ====================== 댓글 좋아요 이벤트 ====================== + + @Test + @DisplayName("댓글 좋아요 이벤트 수신 - 알림 생성 성공") + void t8() { + // given + CommentLikedEvent event = new CommentLikedEvent( + 1L, // actorId + 2L, // receiverId + 100L, // postId + 200L, // commentId, + "댓글 내용" + ); + + given(userRepository.findById(1L)).willReturn(Optional.of(actor)); + given(userRepository.findById(2L)).willReturn(Optional.of(receiver)); + + // when + listener.handleCommentLiked(event); + + // then + verify(notificationService).createCommunityNotification( + eq(receiver), + eq(actor), + anyString(), + anyString(), + eq("/posts/100#comment-200") + ); + } + + @Test + @DisplayName("댓글 좋아요 이벤트 - 작성자와 수신자가 같은 경우") + void t9() { + // given + CommentLikedEvent event = new CommentLikedEvent( + 1L, // actorId + 1L, // receiverId (같은 사람) + 100L, + 200L, + "댓글 내용" + ); + + given(userRepository.findById(1L)).willReturn(Optional.of(actor)); + + // when + listener.handleCommentLiked(event); + + // then + // NotificationService에서 자기 자신 알림 필터링 처리 + verify(notificationService).createCommunityNotification( + eq(actor), // receiver + eq(actor), // actor + anyString(), + anyString(), + anyString() + ); + } + + // ====================== 예외 처리 테스트 ====================== + + @Test + @DisplayName("알림 생성 중 예외 발생 - 로그만 출력하고 예외 전파 안함") + void t10() { + // given + CommentCreatedEvent event = new CommentCreatedEvent( + 1L, 2L, 100L, 200L, "댓글 내용" + ); + + given(userRepository.findById(1L)).willReturn(Optional.of(actor)); + given(userRepository.findById(2L)).willReturn(Optional.of(receiver)); + + willThrow(new RuntimeException("알림 생성 실패")) + .given(notificationService).createCommunityNotification( + any(), any(), anyString(), anyString(), anyString() + ); + + // when & then - 예외가 전파되지 않아야 함 + assertThatCode(() -> listener.handleCommentCreated(event)) + .doesNotThrowAnyException(); + + verify(notificationService).createCommunityNotification( + any(), any(), anyString(), anyString(), anyString() + ); + } +} \ No newline at end of file diff --git a/src/test/java/com/back/domain/notification/event/study/StudyNotificationEventListenerTest.java b/src/test/java/com/back/domain/notification/event/study/StudyNotificationEventListenerTest.java new file mode 100644 index 00000000..da3a21df --- /dev/null +++ b/src/test/java/com/back/domain/notification/event/study/StudyNotificationEventListenerTest.java @@ -0,0 +1,330 @@ +package com.back.domain.notification.event.study; + +import com.back.domain.notification.service.NotificationService; +import com.back.domain.user.entity.User; +import com.back.domain.user.repository.UserRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.LocalDate; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.*; + +@ExtendWith(MockitoExtension.class) +@DisplayName("StudyNotificationEventListener 테스트") +class StudyNotificationEventListenerTest { + + @Mock + private NotificationService notificationService; + + @Mock + private UserRepository userRepository; + + @InjectMocks + private StudyNotificationEventListener listener; + + private User testUser; + + @BeforeEach + void setUp() { + testUser = User.builder() + .id(1L) + .username("testuser") + .email("test@test.com") + .build(); + } + + // ====================== 학습 기록 생성 이벤트 ====================== + + @Test + @DisplayName("학습 기록 생성 이벤트 수신 - 알림 생성 성공") + void t1() { + // given + StudyRecordCreatedEvent event = new StudyRecordCreatedEvent( + 1L, // userId + 100L, // studyRecordId + 50L, // studyPlanId + 3600L // duration (1시간) + ); + + given(userRepository.findById(1L)).willReturn(Optional.of(testUser)); + + // when + listener.handleStudyRecordCreated(event); + + // then + verify(notificationService).createPersonalNotification( + eq(testUser), // receiver (본인) + eq(testUser), // actor (본인) + anyString(), // title + anyString(), // content + eq("/study/records/100") + ); + } + + @Test + @DisplayName("학습 기록 생성 이벤트 - 사용자 없음") + void t2() { + // given + StudyRecordCreatedEvent event = new StudyRecordCreatedEvent( + 999L, // 존재하지 않는 userId + 100L, + 50L, + 3600L + ); + + given(userRepository.findById(999L)).willReturn(Optional.empty()); + + // when + listener.handleStudyRecordCreated(event); + + // then + verify(notificationService, never()).createPersonalNotification( + any(), any(), anyString(), anyString(), anyString() + ); + } + + @Test + @DisplayName("학습 기록 생성 이벤트 - 짧은 학습 시간") + void t3() { + // given + StudyRecordCreatedEvent event = new StudyRecordCreatedEvent( + 1L, + 100L, + 50L, + 300L // 5분 + ); + + given(userRepository.findById(1L)).willReturn(Optional.of(testUser)); + + // when + listener.handleStudyRecordCreated(event); + + // then + verify(notificationService).createPersonalNotification( + eq(testUser), + eq(testUser), + anyString(), + anyString(), + eq("/study/records/100") + ); + } + + @Test + @DisplayName("학습 기록 생성 이벤트 - 긴 학습 시간") + void t4() { + // given + StudyRecordCreatedEvent event = new StudyRecordCreatedEvent( + 1L, + 100L, + 50L, + 14400L // 4시간 + ); + + given(userRepository.findById(1L)).willReturn(Optional.of(testUser)); + + // when + listener.handleStudyRecordCreated(event); + + // then + verify(notificationService).createPersonalNotification( + eq(testUser), + eq(testUser), + anyString(), + anyString(), + eq("/study/records/100") + ); + } + + // ====================== 일일 목표 달성 이벤트 ====================== + + @Test + @DisplayName("일일 목표 달성 이벤트 수신 - 알림 생성 성공") + void t5() { + // given + LocalDate today = LocalDate.now(); + DailyGoalAchievedEvent event = new DailyGoalAchievedEvent( + 1L, // userId + today, // achievedDate + 5, // completedPlans + 5 // totalPlans + ); + + given(userRepository.findById(1L)).willReturn(Optional.of(testUser)); + + // when + listener.handleDailyGoalAchieved(event); + + // then + verify(notificationService).createPersonalNotification( + eq(testUser), // receiver (본인) + eq(testUser), // actor (본인) + anyString(), // title + anyString(), // content + eq("/study/plans?date=" + today) + ); + } + + @Test + @DisplayName("일일 목표 달성 이벤트 - 사용자 없음") + void t6() { + // given + LocalDate today = LocalDate.now(); + DailyGoalAchievedEvent event = new DailyGoalAchievedEvent( + 999L, // 존재하지 않는 userId + today, + 5, + 5 + ); + + given(userRepository.findById(999L)).willReturn(Optional.empty()); + + // when + listener.handleDailyGoalAchieved(event); + + // then + verify(notificationService, never()).createPersonalNotification( + any(), any(), anyString(), anyString(), anyString() + ); + } + + @Test + @DisplayName("일일 목표 달성 이벤트 - 부분 달성") + void t7() { + // given + LocalDate today = LocalDate.now(); + DailyGoalAchievedEvent event = new DailyGoalAchievedEvent( + 1L, + today, + 3, // completedPlans + 5 // totalPlans + ); + + given(userRepository.findById(1L)).willReturn(Optional.of(testUser)); + + // when + listener.handleDailyGoalAchieved(event); + + // then + verify(notificationService).createPersonalNotification( + eq(testUser), + eq(testUser), + anyString(), + anyString(), + eq("/study/plans?date=" + today) + ); + } + + @Test + @DisplayName("일일 목표 달성 이벤트 - 초과 달성") + void t8() { + // given + LocalDate today = LocalDate.now(); + DailyGoalAchievedEvent event = new DailyGoalAchievedEvent( + 1L, + today, + 7, // completedPlans + 5 // totalPlans (목표보다 많이 달성) + ); + + given(userRepository.findById(1L)).willReturn(Optional.of(testUser)); + + // when + listener.handleDailyGoalAchieved(event); + + // then + verify(notificationService).createPersonalNotification( + eq(testUser), + eq(testUser), + anyString(), + anyString(), + eq("/study/plans?date=" + today) + ); + } + + @Test + @DisplayName("일일 목표 달성 이벤트 - 과거 날짜") + void t9() { + // given + LocalDate yesterday = LocalDate.now().minusDays(1); + DailyGoalAchievedEvent event = new DailyGoalAchievedEvent( + 1L, + yesterday, + 5, + 5 + ); + + given(userRepository.findById(1L)).willReturn(Optional.of(testUser)); + + // when + listener.handleDailyGoalAchieved(event); + + // then + verify(notificationService).createPersonalNotification( + eq(testUser), + eq(testUser), + anyString(), + anyString(), + eq("/study/plans?date=" + yesterday) + ); + } + + // ====================== 예외 처리 테스트 ====================== + + @Test + @DisplayName("학습 기록 알림 생성 중 예외 발생 - 로그만 출력하고 예외 전파 안함") + void t10() { + // given + StudyRecordCreatedEvent event = new StudyRecordCreatedEvent( + 1L, 100L, 3600L, 50L + ); + + given(userRepository.findById(1L)).willReturn(Optional.of(testUser)); + + willThrow(new RuntimeException("알림 생성 실패")) + .given(notificationService).createPersonalNotification( + any(), any(), anyString(), anyString(), anyString() + ); + + // when & then - 예외가 전파되지 않아야 함 + assertThatCode(() -> listener.handleStudyRecordCreated(event)) + .doesNotThrowAnyException(); + + verify(notificationService).createPersonalNotification( + any(), any(), anyString(), anyString(), anyString() + ); + } + + @Test + @DisplayName("일일 목표 달성 알림 생성 중 예외 발생 - 로그만 출력하고 예외 전파 안함") + void t11() { + // given + LocalDate today = LocalDate.now(); + DailyGoalAchievedEvent event = new DailyGoalAchievedEvent( + 1L, today, 5, 5 + ); + + given(userRepository.findById(1L)).willReturn(Optional.of(testUser)); + + willThrow(new RuntimeException("알림 생성 실패")) + .given(notificationService).createPersonalNotification( + any(), any(), anyString(), anyString(), anyString() + ); + + // when & then - 예외가 전파되지 않아야 함 + assertThatCode(() -> listener.handleDailyGoalAchieved(event)) + .doesNotThrowAnyException(); + + verify(notificationService).createPersonalNotification( + any(), any(), anyString(), anyString(), anyString() + ); + } +} \ No newline at end of file diff --git a/src/test/java/com/back/domain/notification/event/studyroom/StudyRoomNotificationEventListenerTest.java b/src/test/java/com/back/domain/notification/event/studyroom/StudyRoomNotificationEventListenerTest.java new file mode 100644 index 00000000..25d95ad4 --- /dev/null +++ b/src/test/java/com/back/domain/notification/event/studyroom/StudyRoomNotificationEventListenerTest.java @@ -0,0 +1,371 @@ +package com.back.domain.notification.event.studyroom; + +import com.back.domain.notification.service.NotificationService; +import com.back.domain.studyroom.entity.Room; +import com.back.domain.studyroom.repository.RoomMemberRepository; +import com.back.domain.studyroom.repository.RoomRepository; +import com.back.domain.user.entity.User; +import com.back.domain.user.repository.UserRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.*; + +@ExtendWith(MockitoExtension.class) +@DisplayName("StudyRoomNotificationEventListener 테스트") +class StudyRoomNotificationEventListenerTest { + + @Mock + private NotificationService notificationService; + + @Mock + private RoomRepository roomRepository; + + @Mock + private UserRepository userRepository; + + @Mock + private RoomMemberRepository roomMemberRepository; + + @InjectMocks + private StudyRoomNotificationEventListener listener; + + private User actor; + private User receiver; + private Room room; + + @BeforeEach + void setUp() { + actor = User.builder() + .id(1L) + .username("actor") + .email("actor@test.com") + .build(); + + receiver = User.builder() + .id(2L) + .username("receiver") + .email("receiver@test.com") + .build(); + + room = Room.create( + "테스트 방", + "설명", + false, + null, + 10, + actor, + null, + true + ); + } + + // ====================== 스터디룸 공지사항 이벤트 ====================== + + @Test + @DisplayName("스터디룸 공지사항 생성 이벤트 수신 - 알림 생성 성공") + void t1() { + // given + StudyRoomNoticeCreatedEvent event = new StudyRoomNoticeCreatedEvent( + 1L, // actorId + 100L, // roomId + "공지사항 제목입니다", + "공지사항 내용입니다" + ); + + given(roomRepository.findById(100L)).willReturn(Optional.of(room)); + given(userRepository.findById(1L)).willReturn(Optional.of(actor)); + + // when + listener.handleNoticeCreated(event); + + // then + verify(notificationService).createRoomNotification( + eq(room), + eq(actor), + anyString(), // title + eq("공지사항 제목입니다"), // content (공지 제목) + eq("/rooms/100/notices") + ); + } + + @Test + @DisplayName("스터디룸 공지사항 이벤트 - 방 없음") + void t2() { + // given + StudyRoomNoticeCreatedEvent event = new StudyRoomNoticeCreatedEvent( + 1L, + 999L, // 존재하지 않는 roomId + "공지사항 제목", + "공지사항 내용입니다" + ); + + given(roomRepository.findById(999L)).willReturn(Optional.empty()); + + // when + listener.handleNoticeCreated(event); + + // then + verify(notificationService, never()).createRoomNotification( + any(), any(), anyString(), anyString(), anyString() + ); + } + + @Test + @DisplayName("스터디룸 공지사항 이벤트 - 작성자 없음") + void t3() { + // given + StudyRoomNoticeCreatedEvent event = new StudyRoomNoticeCreatedEvent( + 999L, // 존재하지 않는 actorId + 100L, + "공지사항 제목", + "공지사항 내용입니다" + ); + + given(roomRepository.findById(100L)).willReturn(Optional.of(room)); + given(userRepository.findById(999L)).willReturn(Optional.empty()); + + // when + listener.handleNoticeCreated(event); + + // then + verify(notificationService, never()).createRoomNotification( + any(), any(), anyString(), anyString(), anyString() + ); + } + + // ====================== 멤버 권한 변경 이벤트 ====================== + + @Test + @DisplayName("멤버 권한 변경 이벤트 수신 - 알림 생성 성공") + void t4() { + // given + MemberRoleChangedEvent event = new MemberRoleChangedEvent( + 1L, // actorId (권한 변경한 사람) + 100L, // roomId + 2L, // targetUserId (권한 변경된 사람) + "MANAGER" // newRole + ); + + given(userRepository.findById(2L)).willReturn(Optional.of(receiver)); + given(userRepository.findById(1L)).willReturn(Optional.of(actor)); + + // when + listener.handleMemberRoleChanged(event); + + // then + verify(notificationService).createPersonalNotification( + eq(receiver), // 권한 변경된 사람이 수신자 + eq(actor), // 권한 변경한 사람이 actor + anyString(), + anyString(), + eq("/rooms/100") + ); + } + + @Test + @DisplayName("멤버 권한 변경 이벤트 - 수신자 없음") + void t5() { + // given + MemberRoleChangedEvent event = new MemberRoleChangedEvent( + 1L, + 100L, + 999L, // 존재하지 않는 targetUserId + "MANAGER" + ); + + given(userRepository.findById(999L)).willReturn(Optional.empty()); + + // when + listener.handleMemberRoleChanged(event); + + // then + verify(notificationService, never()).createPersonalNotification( + any(), any(), anyString(), anyString(), anyString() + ); + } + + // ====================== 멤버 추방 이벤트 ====================== + + @Test + @DisplayName("멤버 추방 이벤트 수신 - 알림 생성 성공") + void t6() { + // given + MemberKickedEvent event = new MemberKickedEvent( + 1L, // actorId (추방한 사람) + 100L, // roomId + 2L, // kickedUserId (추방당한 사람) + "테스트 방" + ); + + given(userRepository.findById(2L)).willReturn(Optional.of(receiver)); + given(userRepository.findById(1L)).willReturn(Optional.of(actor)); + + // when + listener.handleMemberKicked(event); + + // then + verify(notificationService).createPersonalNotification( + eq(receiver), // 추방당한 사람이 수신자 + eq(actor), // 추방한 사람이 actor + anyString(), + anyString(), + eq("/rooms") // 방 목록으로 이동 + ); + } + + @Test + @DisplayName("멤버 추방 이벤트 - 추방당한 사람 없음") + void t7() { + // given + MemberKickedEvent event = new MemberKickedEvent( + 1L, + 100L, + 999L, // 존재하지 않는 kickedUserId + "테스트 방" + ); + + given(userRepository.findById(999L)).willReturn(Optional.empty()); + + // when + listener.handleMemberKicked(event); + + // then + verify(notificationService, never()).createPersonalNotification( + any(), any(), anyString(), anyString(), anyString() + ); + } + + @Test + @DisplayName("멤버 추방 이벤트 - 추방한 사람 없음") + void t8() { + // given + MemberKickedEvent event = new MemberKickedEvent( + 999L, // 존재하지 않는 actorId + 100L, + 2L, + "테스트 방" + ); + + given(userRepository.findById(2L)).willReturn(Optional.of(receiver)); + given(userRepository.findById(999L)).willReturn(Optional.empty()); + + // when + listener.handleMemberKicked(event); + + // then + verify(notificationService, never()).createPersonalNotification( + any(), any(), anyString(), anyString(), anyString() + ); + } + + // ====================== 방장 위임 이벤트 ====================== + + @Test + @DisplayName("방장 위임 이벤트 수신 - 알림 생성 성공") + void t9() { + // given + OwnerTransferredEvent event = new OwnerTransferredEvent( + 1L, // actorId (이전 방장) + 100L, // roomId + 2L, // newOwnerId (새 방장) + "테스트 방" + ); + + given(userRepository.findById(2L)).willReturn(Optional.of(receiver)); + given(userRepository.findById(1L)).willReturn(Optional.of(actor)); + + // when + listener.handleOwnerTransferred(event); + + // then + verify(notificationService).createPersonalNotification( + eq(receiver), // 새 방장이 수신자 + eq(actor), // 이전 방장이 actor + anyString(), + anyString(), + eq("/rooms/100") + ); + } + + @Test + @DisplayName("방장 위임 이벤트 - 새 방장 없음") + void t10() { + // given + OwnerTransferredEvent event = new OwnerTransferredEvent( + 1L, + 100L, + 999L, // 존재하지 않는 newOwnerId + "테스트 방" + ); + + given(userRepository.findById(999L)).willReturn(Optional.empty()); + + // when + listener.handleOwnerTransferred(event); + + // then + verify(notificationService, never()).createPersonalNotification( + any(), any(), anyString(), anyString(), anyString() + ); + } + + @Test + @DisplayName("방장 위임 이벤트 - 이전 방장 없음") + void t11() { + // given + OwnerTransferredEvent event = new OwnerTransferredEvent( + 999L, // 존재하지 않는 actorId + 100L, + 2L, + "테스트 방" + ); + + given(userRepository.findById(2L)).willReturn(Optional.of(receiver)); + given(userRepository.findById(999L)).willReturn(Optional.empty()); + + // when + listener.handleOwnerTransferred(event); + + // then + verify(notificationService, never()).createPersonalNotification( + any(), any(), anyString(), anyString(), anyString() + ); + } + + // ====================== 예외 처리 테스트 ====================== + + @Test + @DisplayName("알림 생성 중 예외 발생 - 로그만 출력하고 예외 전파 안함") + void t12() { + // given + MemberRoleChangedEvent event = new MemberRoleChangedEvent( + 1L, 100L, 2L, "MANAGER" + ); + + given(userRepository.findById(2L)).willReturn(Optional.of(receiver)); + given(userRepository.findById(1L)).willReturn(Optional.of(actor)); + + willThrow(new RuntimeException("알림 생성 실패")) + .given(notificationService).createPersonalNotification( + any(), any(), anyString(), anyString(), anyString() + ); + + // when & then - 예외가 전파되지 않아야 함 + assertThatCode(() -> listener.handleMemberRoleChanged(event)) + .doesNotThrowAnyException(); + + verify(notificationService).createPersonalNotification( + any(), any(), anyString(), anyString(), anyString() + ); + } +} \ No newline at end of file diff --git a/src/test/java/com/back/domain/studyroom/service/RoomServiceTest.java b/src/test/java/com/back/domain/studyroom/service/RoomServiceTest.java index 0ebb296f..b376bca0 100644 --- a/src/test/java/com/back/domain/studyroom/service/RoomServiceTest.java +++ b/src/test/java/com/back/domain/studyroom/service/RoomServiceTest.java @@ -1,5 +1,6 @@ package com.back.domain.studyroom.service; +import com.back.domain.notification.service.NotificationService; import com.back.domain.studyroom.config.StudyRoomProperties; import com.back.domain.studyroom.entity.*; import com.back.domain.studyroom.repository.RoomMemberRepository; @@ -55,6 +56,9 @@ class RoomServiceTest { @Mock private ApplicationEventPublisher eventPublisher; + @Mock + private NotificationService notificationService; + @InjectMocks private RoomService roomService;