test: add CallControllerTest unit test

This commit is contained in:
5803024019 2026-05-16 08:11:43 +07:00
parent c26321f797
commit 785d883556

View File

@ -0,0 +1,429 @@
package com.walkguide.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.walkguide.dto.request.CallNotifyRequest;
import com.walkguide.dto.request.CallTokenRequest;
import com.walkguide.dto.response.AgoraTokenResponse;
import com.walkguide.entity.User;
import com.walkguide.exception.ResourceNotFoundException;
import com.walkguide.repository.UserRepository;
import com.walkguide.security.SecurityHelper;
import com.walkguide.service.AgoraTokenService;
import com.walkguide.service.FcmService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;
import java.util.Map;
import java.util.Optional;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@WebMvcTest(CallController.class)
@WithMockUser(username = "1", roles = "USER")
@DisplayName("CallController Unit Tests")
class CallControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@MockBean
private AgoraTokenService agoraTokenService;
@MockBean
private FcmService fcmService;
@MockBean
private UserRepository userRepository;
private User sampleCaller;
private User sampleReceiver;
@BeforeEach
void setUp() {
sampleCaller = User.builder()
.id(1L)
.email("caller@test.com")
.displayName("Caller User")
.fcmToken("fcm-caller-token")
.build();
sampleReceiver = User.builder()
.id(2L)
.email("receiver@test.com")
.displayName("Receiver Guardian")
.fcmToken("fcm-receiver-token")
.build();
}
// ===== GENERATE TOKEN =====
@Test
@DisplayName("POST /api/v1/shared/call/token - valid request harus return Agora token")
void generateToken_validRequest_shouldReturn200() throws Exception {
try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L);
CallTokenRequest req = new CallTokenRequest();
req.setReceiverId(2L);
AgoraTokenResponse tokenResp = AgoraTokenResponse.builder()
.token("agora-rtc-token-xyz")
.channelName("call_1_2_1234567890")
.callerUid(1001)
.receiverUid(1002)
.build();
when(agoraTokenService.generateToken(1L, 2L)).thenReturn(tokenResp);
mockMvc.perform(post("/api/v1/shared/call/token")
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(req)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.success").value(true))
.andExpect(jsonPath("$.message").value("Token Agora berhasil digenerate"))
.andExpect(jsonPath("$.data.token").value("agora-rtc-token-xyz"))
.andExpect(jsonPath("$.data.channelName").value("call_1_2_1234567890"));
verify(agoraTokenService).generateToken(1L, 2L);
}
}
@Test
@DisplayName("POST /api/v1/shared/call/token - receiverId null harus return 400")
void generateToken_nullReceiverId_shouldReturn400() throws Exception {
try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L);
// CallTokenRequest dengan receiverId null @Valid harus menolak
CallTokenRequest req = new CallTokenRequest();
// receiverId tidak di-set (null)
mockMvc.perform(post("/api/v1/shared/call/token")
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(req)))
.andExpect(status().isBadRequest());
verify(agoraTokenService, never()).generateToken(anyLong(), anyLong());
}
}
@Test
@DisplayName("POST /api/v1/shared/call/token - service throw harus return 500")
void generateToken_serviceThrows_shouldReturn500() throws Exception {
try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L);
CallTokenRequest req = new CallTokenRequest();
req.setReceiverId(99L);
when(agoraTokenService.generateToken(1L, 99L))
.thenThrow(new RuntimeException("Receiver tidak ditemukan"));
mockMvc.perform(post("/api/v1/shared/call/token")
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(req)))
.andExpect(status().isInternalServerError());
}
}
// ===== NOTIFY CALL =====
@Test
@DisplayName("POST /api/v1/shared/call/notify - receiver punya FCM token harus kirim notifikasi")
void notifyCall_receiverHasFcmToken_shouldReturn200AndSendFcm() throws Exception {
try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L);
when(userRepository.findById(1L)).thenReturn(Optional.of(sampleCaller));
when(userRepository.findById(2L)).thenReturn(Optional.of(sampleReceiver));
doNothing().when(fcmService).sendHighPriority(anyString(), anyString(), anyString(), anyMap());
CallNotifyRequest req = new CallNotifyRequest();
req.setReceiverId(2L);
req.setChannelName("call_1_2_1234567890");
req.setAgoraToken("agora-token-xyz");
req.setReceiverUid(1002);
mockMvc.perform(post("/api/v1/shared/call/notify")
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(req)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.success").value(true))
.andExpect(jsonPath("$.message").value("Notifikasi panggilan berhasil dikirim"));
verify(fcmService).sendHighPriority(
eq("fcm-receiver-token"),
eq("📞 Panggilan Masuk"),
contains("Caller User"),
anyMap()
);
}
}
@Test
@DisplayName("POST /api/v1/shared/call/notify - receiver tidak punya FCM token harus return 200 tanpa FCM")
void notifyCall_receiverNoFcmToken_shouldReturn200WithWarningMessage() throws Exception {
try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L);
User receiverNoFcm = User.builder()
.id(2L)
.email("receiver@test.com")
.displayName("Receiver")
.fcmToken(null) // tidak punya FCM token
.build();
when(userRepository.findById(1L)).thenReturn(Optional.of(sampleCaller));
when(userRepository.findById(2L)).thenReturn(Optional.of(receiverNoFcm));
CallNotifyRequest req = new CallNotifyRequest();
req.setReceiverId(2L);
req.setChannelName("call_1_2_abc");
req.setAgoraToken("token-xyz");
req.setReceiverUid(1002);
mockMvc.perform(post("/api/v1/shared/call/notify")
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(req)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.message").value(
"Panggilan dikirim (receiver mungkin tidak menerima push notification)"));
// FCM tidak boleh dipanggil karena tidak ada token
verify(fcmService, never()).sendHighPriority(anyString(), anyString(), anyString(), anyMap());
}
}
@Test
@DisplayName("POST /api/v1/shared/call/notify - receiver FCM token blank harus return 200 tanpa FCM")
void notifyCall_receiverBlankFcmToken_shouldReturn200WithoutFcm() throws Exception {
try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L);
User receiverBlankFcm = User.builder()
.id(2L)
.email("receiver@test.com")
.displayName("Receiver")
.fcmToken(" ") // blank
.build();
when(userRepository.findById(1L)).thenReturn(Optional.of(sampleCaller));
when(userRepository.findById(2L)).thenReturn(Optional.of(receiverBlankFcm));
CallNotifyRequest req = new CallNotifyRequest();
req.setReceiverId(2L);
req.setChannelName("channel-abc");
req.setAgoraToken("token");
req.setReceiverUid(1002);
mockMvc.perform(post("/api/v1/shared/call/notify")
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(req)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.message").value(
"Panggilan dikirim (receiver mungkin tidak menerima push notification)"));
verify(fcmService, never()).sendHighPriority(anyString(), anyString(), anyString(), anyMap());
}
}
@Test
@DisplayName("POST /api/v1/shared/call/notify - caller tidak ditemukan harus return 500")
void notifyCall_callerNotFound_shouldReturn500() throws Exception {
try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L);
when(userRepository.findById(1L))
.thenThrow(new ResourceNotFoundException("Caller not found"));
CallNotifyRequest req = new CallNotifyRequest();
req.setReceiverId(2L);
req.setChannelName("channel-abc");
req.setAgoraToken("token");
req.setReceiverUid(1002);
mockMvc.perform(post("/api/v1/shared/call/notify")
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(req)))
.andExpect(status().isInternalServerError());
verify(fcmService, never()).sendHighPriority(anyString(), anyString(), anyString(), anyMap());
}
}
@Test
@DisplayName("POST /api/v1/shared/call/notify - receiver tidak ditemukan harus return 500")
void notifyCall_receiverNotFound_shouldReturn500() throws Exception {
try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L);
when(userRepository.findById(1L)).thenReturn(Optional.of(sampleCaller));
when(userRepository.findById(2L))
.thenThrow(new ResourceNotFoundException("Receiver not found"));
CallNotifyRequest req = new CallNotifyRequest();
req.setReceiverId(2L);
req.setChannelName("channel-abc");
req.setAgoraToken("token");
req.setReceiverUid(1002);
mockMvc.perform(post("/api/v1/shared/call/notify")
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(req)))
.andExpect(status().isInternalServerError());
}
}
@Test
@DisplayName("POST /api/v1/shared/call/notify - caller displayName null harus pakai email sebagai pengganti")
void notifyCall_callerNoDisplayName_shouldUseEmailAsFallback() throws Exception {
try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L);
User callerNoName = User.builder()
.id(1L)
.email("noreply@test.com")
.displayName(null) // tidak ada displayName
.build();
when(userRepository.findById(1L)).thenReturn(Optional.of(callerNoName));
when(userRepository.findById(2L)).thenReturn(Optional.of(sampleReceiver));
doNothing().when(fcmService).sendHighPriority(anyString(), anyString(), anyString(), anyMap());
CallNotifyRequest req = new CallNotifyRequest();
req.setReceiverId(2L);
req.setChannelName("channel-abc");
req.setAgoraToken("token");
req.setReceiverUid(1002);
mockMvc.perform(post("/api/v1/shared/call/notify")
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(req)))
.andExpect(status().isOk());
// Pastikan body notifikasi menggunakan email sebagai fallback
verify(fcmService).sendHighPriority(
anyString(),
anyString(),
contains("noreply@test.com"),
anyMap()
);
}
}
// ===== END CALL =====
@Test
@DisplayName("POST /api/v1/shared/call/end - otherId valid dan punya FCM token harus kirim notifikasi berakhir")
void endCall_otherHasFcmToken_shouldSendEndNotification() throws Exception {
try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L);
when(userRepository.findById(2L)).thenReturn(Optional.of(sampleReceiver));
doNothing().when(fcmService).sendToToken(anyString(), anyString(), anyString(), anyMap());
Map<String, Long> body = Map.of("otherId", 2L);
mockMvc.perform(post("/api/v1/shared/call/end")
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(body)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.success").value(true))
.andExpect(jsonPath("$.message").value("Call ended"));
verify(fcmService).sendToToken(
eq("fcm-receiver-token"),
eq("Panggilan Berakhir"),
eq("Panggilan telah berakhir"),
anyMap()
);
}
}
@Test
@DisplayName("POST /api/v1/shared/call/end - otherId null harus return 200 tanpa kirim FCM")
void endCall_nullOtherId_shouldReturn200WithoutFcm() throws Exception {
try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L);
Map<String, Long> body = Map.of(); // tidak ada otherId
mockMvc.perform(post("/api/v1/shared/call/end")
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(body)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.message").value("Call ended"));
verify(fcmService, never()).sendToToken(anyString(), anyString(), anyString(), anyMap());
}
}
@Test
@DisplayName("POST /api/v1/shared/call/end - other tidak punya FCM token harus return 200 tanpa FCM")
void endCall_otherNoFcmToken_shouldReturn200WithoutFcm() throws Exception {
try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L);
User otherNoFcm = User.builder()
.id(2L)
.email("other@test.com")
.fcmToken(null)
.build();
when(userRepository.findById(2L)).thenReturn(Optional.of(otherNoFcm));
Map<String, Long> body = Map.of("otherId", 2L);
mockMvc.perform(post("/api/v1/shared/call/end")
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(body)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.message").value("Call ended"));
verify(fcmService, never()).sendToToken(anyString(), anyString(), anyString(), anyMap());
}
}
@Test
@WithMockUser(username = "2", roles = "GUARDIAN")
@DisplayName("POST /api/v1/shared/call/end - Guardian juga bisa end call")
void endCall_asGuardian_shouldReturn200() throws Exception {
try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L);
when(userRepository.findById(1L)).thenReturn(Optional.of(sampleCaller));
doNothing().when(fcmService).sendToToken(anyString(), anyString(), anyString(), anyMap());
Map<String, Long> body = Map.of("otherId", 1L);
mockMvc.perform(post("/api/v1/shared/call/end")
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(body)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.message").value("Call ended"));
}
}
}