package com.walkguide.service; import com.walkguide.dto.request.CallNotifyRequest; import com.walkguide.entity.User; import com.walkguide.exception.ResourceNotFoundException; import com.walkguide.repository.UserRepository; import com.walkguide.websocket.LocationBroadcaster; 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.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.contains; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @DisplayName("CallNotificationService Unit Tests") class CallNotificationServiceTest { @Mock private FcmService fcmService; @Mock private UserRepository userRepository; @Mock private LocationBroadcaster locationBroadcaster; @InjectMocks private CallNotificationService service; private User caller; private User receiver; private CallNotifyRequest request; @BeforeEach void setUp() { caller = User.builder() .id(1L) .email("caller@test.com") .displayName("Caller User") .build(); receiver = User.builder() .id(2L) .email("receiver@test.com") .displayName("Receiver") .fcmToken("receiver-token") .build(); request = new CallNotifyRequest(); request.setReceiverId(2L); request.setChannelName("call_1_2"); request.setAgoraToken("agora-token"); request.setReceiverUid(1002); } @Test @DisplayName("notifyIncomingCall sends high priority FCM when receiver has token") void notifyIncomingCall_receiverHasToken_shouldSendFcm() { when(userRepository.findById(1L)).thenReturn(Optional.of(caller)); when(userRepository.findById(2L)).thenReturn(Optional.of(receiver)); String message = service.notifyIncomingCall(1L, request); assertEquals("Notifikasi panggilan berhasil dikirim", message); verify(fcmService).sendHighPriority( eq("receiver-token"), eq("Panggilan Masuk"), contains("Caller User"), anyMap() ); } @Test @DisplayName("notifyIncomingCall skips FCM when receiver token is missing") void notifyIncomingCall_receiverHasNoToken_shouldSkipFcm() { receiver.setFcmToken(null); when(userRepository.findById(1L)).thenReturn(Optional.of(caller)); when(userRepository.findById(2L)).thenReturn(Optional.of(receiver)); String message = service.notifyIncomingCall(1L, request); assertEquals("Panggilan dikirim via realtime fallback.", message); verify(fcmService, never()).sendHighPriority(anyString(), anyString(), anyString(), anyMap()); } @Test @DisplayName("notifyIncomingCall throws 404 when caller is missing") void notifyIncomingCall_missingCaller_shouldThrow() { when(userRepository.findById(1L)).thenReturn(Optional.empty()); assertThrows(ResourceNotFoundException.class, () -> service.notifyIncomingCall(1L, request)); verify(fcmService, never()).sendHighPriority(anyString(), anyString(), anyString(), anyMap()); } @Test @DisplayName("notifyCallEnded sends FCM when other user has token") void notifyCallEnded_otherHasToken_shouldSendFcm() { when(userRepository.findById(2L)).thenReturn(Optional.of(receiver)); service.notifyCallEnded(1L, 2L); verify(fcmService).sendToToken( eq("receiver-token"), eq("Panggilan Berakhir"), eq("Panggilan telah berakhir"), anyMap() ); } @Test @DisplayName("notifyCallEnded skips repository lookup when otherId is null") void notifyCallEnded_nullOtherId_shouldReturn() { service.notifyCallEnded(1L, null); verify(userRepository, never()).findById(2L); verify(fcmService, never()).sendToToken(anyString(), anyString(), anyString(), anyMap()); } }