From f9a23dee5863280b1a36effaddc3fc1d50ddfbcd Mon Sep 17 00:00:00 2001 From: 5803024019 Date: Sat, 16 May 2026 08:17:27 +0700 Subject: [PATCH] test: add UserControllerTest unit test --- .../Controller/UserControllerTest.java | 391 ++++++++++++++++++ 1 file changed, 391 insertions(+) create mode 100644 walkguide-backend/demo/src/test/java/com/walkguide/Controller/UserControllerTest.java diff --git a/walkguide-backend/demo/src/test/java/com/walkguide/Controller/UserControllerTest.java b/walkguide-backend/demo/src/test/java/com/walkguide/Controller/UserControllerTest.java new file mode 100644 index 0000000..e969880 --- /dev/null +++ b/walkguide-backend/demo/src/test/java/com/walkguide/Controller/UserControllerTest.java @@ -0,0 +1,391 @@ +package com.walkguide.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.walkguide.dto.ApiResponse; +import com.walkguide.dto.request.*; +import com.walkguide.dto.response.*; +import com.walkguide.entity.User; +import com.walkguide.enums.ActivityLogType; +import com.walkguide.repository.UserRepository; +import com.walkguide.security.SecurityHelper; +import com.walkguide.service.*; +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.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.List; +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(UserController.class) +@WithMockUser(username = "1", roles = "USER") +@DisplayName("UserController Unit Tests") +class UserControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean private LocationService locationService; + @MockBean private ObstacleLogService obstacleLogService; + @MockBean private SosService sosService; + @MockBean private ActivityLogService activityLogService; + @MockBean private NotificationService notificationService; + @MockBean private UserSettingsService userSettingsService; + @MockBean private AiConfigService aiConfigService; + @MockBean private VoiceCommandService voiceCommandService; + @MockBean private HardwareShortcutService hardwareShortcutService; + @MockBean private UserRepository userRepository; + + private User sampleUser; + + @BeforeEach + void setUp() { + sampleUser = User.builder() + .id(1L) + .email("user@test.com") + .displayName("Test User") + .role("ROLE_USER") + .uniqueUserId("ABC123DEF456") + .build(); + } + + // ===== PROFILE ===== + + @Test + @DisplayName("GET /api/v1/user/profile - harus return profil user") + void getProfile_shouldReturn200WithProfile() throws Exception { + try (MockedStatic sh = mockStatic(SecurityHelper.class)) { + sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L); + when(userRepository.findById(1L)).thenReturn(Optional.of(sampleUser)); + + mockMvc.perform(get("/api/v1/user/profile")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.data.email").value("user@test.com")) + .andExpect(jsonPath("$.data.uniqueUserId").value("ABC123DEF456")); + } + } + + @Test + @DisplayName("GET /api/v1/user/profile - user tidak ditemukan harus return 500") + void getProfile_userNotFound_shouldReturn500() throws Exception { + try (MockedStatic sh = mockStatic(SecurityHelper.class)) { + sh.when(SecurityHelper::getCurrentUserId).thenReturn(99L); + when(userRepository.findById(99L)).thenReturn(Optional.empty()); + + mockMvc.perform(get("/api/v1/user/profile")) + .andExpect(status().isInternalServerError()); + } + } + + // ===== SETTINGS ===== + + @Test + @DisplayName("GET /api/v1/user/settings - harus return settings user") + void getSettings_shouldReturn200() throws Exception { + try (MockedStatic sh = mockStatic(SecurityHelper.class)) { + sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L); + + UserSettingsResponse settings = new UserSettingsResponse(); + when(userSettingsService.getSettings(1L)).thenReturn(settings); + + mockMvc.perform(get("/api/v1/user/settings")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.message").value("Settings user")); + } + } + + @Test + @DisplayName("PUT /api/v1/user/settings - harus update dan return settings baru") + void updateSettings_shouldReturn200() throws Exception { + try (MockedStatic sh = mockStatic(SecurityHelper.class)) { + sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L); + + UserSettingsUpdateRequest req = new UserSettingsUpdateRequest(); + UserSettingsResponse updated = new UserSettingsResponse(); + when(userSettingsService.updateSettings(eq(1L), any())).thenReturn(updated); + + mockMvc.perform(put("/api/v1/user/settings") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(req))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.message").value("Settings diperbarui")); + + verify(userSettingsService).updateSettings(eq(1L), any(UserSettingsUpdateRequest.class)); + } + } + + // ===== VOICE COMMANDS ===== + + @Test + @DisplayName("GET /api/v1/user/voice-commands - harus return list voice commands") + void getVoiceCommands_shouldReturn200() throws Exception { + try (MockedStatic sh = mockStatic(SecurityHelper.class)) { + sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L); + + VoiceCommandResponse cmd = new VoiceCommandResponse(); + when(voiceCommandService.getAll(1L)).thenReturn(List.of(cmd)); + + mockMvc.perform(get("/api/v1/user/voice-commands")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.data").isArray()); + } + } + + // ===== SHORTCUTS ===== + + @Test + @DisplayName("GET /api/v1/user/shortcuts - harus return list shortcuts") + void getShortcuts_shouldReturn200() throws Exception { + try (MockedStatic sh = mockStatic(SecurityHelper.class)) { + sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L); + when(hardwareShortcutService.getAll(1L)).thenReturn(List.of()); + + mockMvc.perform(get("/api/v1/user/shortcuts")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)); + } + } + + @Test + @DisplayName("PUT /api/v1/user/shortcuts - harus update shortcut") + void updateShortcut_shouldReturn200() throws Exception { + try (MockedStatic sh = mockStatic(SecurityHelper.class)) { + sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L); + + HardwareShortcutUpdateRequest req = new HardwareShortcutUpdateRequest(); + HardwareShortcutResponse resp = new HardwareShortcutResponse(); + when(hardwareShortcutService.update(eq(1L), any())).thenReturn(resp); + + mockMvc.perform(put("/api/v1/user/shortcuts") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(req))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.message").value("Shortcut diperbarui")); + } + } + + // ===== AI CONFIG ===== + + @Test + @DisplayName("GET /api/v1/user/ai-config - harus return AI config") + void getAiConfig_shouldReturn200() throws Exception { + try (MockedStatic sh = mockStatic(SecurityHelper.class)) { + sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L); + when(aiConfigService.getConfig(1L)).thenReturn(new AiConfigResponse()); + + mockMvc.perform(get("/api/v1/user/ai-config")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.message").value("AI config")); + } + } + + // ===== LOCATION ===== + + @Test + @DisplayName("POST /api/v1/user/location - harus update lokasi dan return LocationResponse") + void updateLocation_shouldReturn200() throws Exception { + try (MockedStatic sh = mockStatic(SecurityHelper.class)) { + sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L); + + LocationUpdateRequest req = new LocationUpdateRequest(); + req.setLatitude(-7.2575); + req.setLongitude(112.7521); + + LocationResponse resp = new LocationResponse(); + when(locationService.updateLocation(eq(1L), any())).thenReturn(resp); + + mockMvc.perform(post("/api/v1/user/location") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(req))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.message").value("Lokasi diperbarui")); + + verify(locationService).updateLocation(eq(1L), any(LocationUpdateRequest.class)); + } + } + + // ===== OBSTACLE ===== + + @Test + @DisplayName("POST /api/v1/user/obstacle - harus mencatat obstacle dan return response") + void logObstacle_shouldReturn200() throws Exception { + try (MockedStatic sh = mockStatic(SecurityHelper.class)) { + sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L); + + ObstacleLogRequest req = new ObstacleLogRequest(); + ObstacleLogResponse resp = new ObstacleLogResponse(); + when(obstacleLogService.saveObstacle(eq(1L), any())).thenReturn(resp); + + mockMvc.perform(post("/api/v1/user/obstacle") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(req))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.message").value("Obstacle dicatat")); + } + } + + // ===== SOS ===== + + @Test + @DisplayName("POST /api/v1/user/sos - harus trigger SOS dan return SosEventResponse") + void triggerSos_shouldReturn200() throws Exception { + try (MockedStatic sh = mockStatic(SecurityHelper.class)) { + sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L); + + SosRequest req = new SosRequest(); + SosEventResponse resp = new SosEventResponse(); + when(sosService.triggerSos(eq(1L), any())).thenReturn(resp); + + mockMvc.perform(post("/api/v1/user/sos") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(req))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.message").value("SOS dikirim! Guardian sudah diberitahu.")); + + verify(sosService).triggerSos(eq(1L), any(SosRequest.class)); + } + } + + // ===== ACTIVITY LOGS ===== + + @Test + @DisplayName("GET /api/v1/user/activity-logs - harus return paginated activity logs") + void getActivityLogs_shouldReturn200() throws Exception { + try (MockedStatic sh = mockStatic(SecurityHelper.class)) { + sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L); + + Page page = new PageImpl<>(List.of()); + when(activityLogService.getLogs(eq(1L), any(PageRequest.class))).thenReturn(page); + + mockMvc.perform(get("/api/v1/user/activity-logs") + .param("page", "0") + .param("size", "10")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.message").value("Log aktivitas")); + } + } + + // ===== NOTIFICATIONS ===== + + @Test + @DisplayName("GET /api/v1/user/notifications - harus return paginated notifikasi") + void getNotifications_shouldReturn200() throws Exception { + try (MockedStatic sh = mockStatic(SecurityHelper.class)) { + sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L); + + Page page = new PageImpl<>(List.of()); + when(notificationService.getNotifications(eq(1L), any())).thenReturn(page); + + mockMvc.perform(get("/api/v1/user/notifications")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.message").value("Notifikasi")); + } + } + + @Test + @DisplayName("GET /api/v1/user/notifications/unread-count - harus return jumlah belum dibaca") + void getUnreadCount_shouldReturn200() throws Exception { + try (MockedStatic sh = mockStatic(SecurityHelper.class)) { + sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L); + when(notificationService.getUnreadCount(1L)).thenReturn(5L); + + mockMvc.perform(get("/api/v1/user/notifications/unread-count")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data").value(5)) + .andExpect(jsonPath("$.message").value("Jumlah notifikasi belum dibaca")); + } + } + + @Test + @DisplayName("PUT /api/v1/user/notifications/mark-all-read - harus mark semua sebagai dibaca") + void markAllRead_shouldReturn200() throws Exception { + try (MockedStatic sh = mockStatic(SecurityHelper.class)) { + sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L); + doNothing().when(notificationService).markAllRead(1L); + + mockMvc.perform(put("/api/v1/user/notifications/mark-all-read") + .with(csrf())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.message").value("Semua notifikasi ditandai sudah dibaca")); + } + } + + @Test + @DisplayName("PUT /api/v1/user/notifications/{id}/read - harus mark satu notifikasi sebagai dibaca") + void markOneRead_shouldReturn200() throws Exception { + doNothing().when(notificationService).markOneRead(42L); + + mockMvc.perform(put("/api/v1/user/notifications/42/read") + .with(csrf())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.message").value("Notifikasi ditandai sudah dibaca")); + + verify(notificationService).markOneRead(42L); + } + + // ===== WALKGUIDE START / STOP ===== + + @Test + @DisplayName("POST /api/v1/user/walkguide/start - harus catat log WALKGUIDE_START") + void walkGuideStart_shouldReturn200() throws Exception { + try (MockedStatic sh = mockStatic(SecurityHelper.class)) { + sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L); + when(userRepository.findById(1L)).thenReturn(Optional.of(sampleUser)); + doNothing().when(activityLogService).createLog(any(), eq(ActivityLogType.WALKGUIDE_START), anyString(), any()); + + mockMvc.perform(post("/api/v1/user/walkguide/start") + .with(csrf())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.message").value("WalkGuide dimulai")); + + verify(activityLogService).createLog(eq(sampleUser), eq(ActivityLogType.WALKGUIDE_START), anyString(), any()); + } + } + + @Test + @DisplayName("POST /api/v1/user/walkguide/stop - harus catat log WALKGUIDE_STOP") + void walkGuideStop_shouldReturn200() throws Exception { + try (MockedStatic sh = mockStatic(SecurityHelper.class)) { + sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L); + when(userRepository.findById(1L)).thenReturn(Optional.of(sampleUser)); + doNothing().when(activityLogService).createLog(any(), eq(ActivityLogType.WALKGUIDE_STOP), anyString(), any()); + + mockMvc.perform(post("/api/v1/user/walkguide/stop") + .with(csrf())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.message").value("WalkGuide dihentikan")); + + verify(activityLogService).createLog(eq(sampleUser), eq(ActivityLogType.WALKGUIDE_STOP), anyString(), any()); + } + } +}