diff --git a/walkguide-backend/demo/src/test/java/com/walkguide/Controller/GuardianControllerTest.java b/walkguide-backend/demo/src/test/java/com/walkguide/Controller/GuardianControllerTest.java new file mode 100644 index 0000000..5ca9ba8 --- /dev/null +++ b/walkguide-backend/demo/src/test/java/com/walkguide/Controller/GuardianControllerTest.java @@ -0,0 +1,363 @@ +package com.walkguide.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.walkguide.dto.request.*; +import com.walkguide.dto.response.*; +import com.walkguide.security.SecurityHelper; +import com.walkguide.service.*; +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(GuardianController.class) +@WithMockUser(username = "2", roles = "GUARDIAN") +@DisplayName("GuardianController Unit Tests") +class GuardianControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean private GuardianDashboardService dashboardService; + @MockBean private LocationService locationService; + @MockBean private ActivityLogService activityLogService; + @MockBean private ObstacleLogService obstacleLogService; + @MockBean private NotificationService notificationService; + @MockBean private SosService sosService; + @MockBean private AiConfigService aiConfigService; + @MockBean private VoiceCommandService voiceCommandService; + @MockBean private HardwareShortcutService hardwareShortcutService; + @MockBean private GeofenceService geofenceService; + @MockBean private UserSettingsService userSettingsService; + + // ===== DASHBOARD ===== + + @Test + @DisplayName("GET /api/v1/guardian/dashboard - harus return dashboard") + void dashboard_shouldReturn200() throws Exception { + try (MockedStatic sh = mockStatic(SecurityHelper.class)) { + sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L); + when(dashboardService.getDashboard(2L)).thenReturn(new DashboardResponse()); + + mockMvc.perform(get("/api/v1/guardian/dashboard")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.message").value("Dashboard Guardian")); + } + } + + // ===== USER LOCATION ===== + + @Test + @DisplayName("GET /api/v1/guardian/user-location - harus return lokasi terakhir user") + void userLocation_locationExists_shouldReturn200() throws Exception { + try (MockedStatic sh = mockStatic(SecurityHelper.class)) { + sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L); + when(locationService.getLastLocationForGuardian(2L)) + .thenReturn(Optional.of(new LocationResponse())); + + mockMvc.perform(get("/api/v1/guardian/user-location")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.message").value("Lokasi terakhir user")); + } + } + + @Test + @DisplayName("GET /api/v1/guardian/user-location - belum ada lokasi harus return data null") + void userLocation_noLocation_shouldReturnNullData() throws Exception { + try (MockedStatic sh = mockStatic(SecurityHelper.class)) { + sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L); + when(locationService.getLastLocationForGuardian(2L)).thenReturn(Optional.empty()); + + mockMvc.perform(get("/api/v1/guardian/user-location")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data").isEmpty()); + } + } + + // ===== LOCATION HISTORY ===== + + @Test + @DisplayName("GET /api/v1/guardian/location-history - harus return paginated history") + void locationHistory_shouldReturn200() throws Exception { + try (MockedStatic sh = mockStatic(SecurityHelper.class)) { + sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L); + + Page page = new PageImpl<>(List.of()); + when(locationService.getLocationHistory(eq(2L), any())).thenReturn(page); + + mockMvc.perform(get("/api/v1/guardian/location-history") + .param("page", "0").param("size", "20")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.message").value("Riwayat lokasi")); + } + } + + // ===== ACTIVITY LOGS ===== + + @Test + @DisplayName("GET /api/v1/guardian/activity-logs - harus return log aktivitas user") + void activityLogs_shouldReturn200() throws Exception { + try (MockedStatic sh = mockStatic(SecurityHelper.class)) { + sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L); + + Page page = new PageImpl<>(List.of()); + when(activityLogService.getLogsForGuardian(eq(2L), any())).thenReturn(page); + + mockMvc.perform(get("/api/v1/guardian/activity-logs")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.message").value("Log aktivitas user")); + } + } + + // ===== OBSTACLE LOGS ===== + + @Test + @DisplayName("GET /api/v1/guardian/obstacle-logs - harus return log obstacle user") + void obstacleLogs_shouldReturn200() throws Exception { + try (MockedStatic sh = mockStatic(SecurityHelper.class)) { + sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L); + + Page page = new PageImpl<>(List.of()); + when(obstacleLogService.getObstacleLogs(eq(2L), any())).thenReturn(page); + + mockMvc.perform(get("/api/v1/guardian/obstacle-logs")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.message").value("Log obstacle user")); + } + } + + // ===== SEND NOTIFICATION ===== + + @Test + @DisplayName("POST /api/v1/guardian/notifications/send - harus kirim notifikasi dan return response") + void sendNotif_shouldReturn200() throws Exception { + try (MockedStatic sh = mockStatic(SecurityHelper.class)) { + sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L); + + SendNotificationRequest req = new SendNotificationRequest(); + NotificationResponse resp = new NotificationResponse(); + when(notificationService.sendNotification(eq(2L), any())).thenReturn(resp); + + mockMvc.perform(post("/api/v1/guardian/notifications/send") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(req))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.message").value("Notifikasi terkirim")); + + verify(notificationService).sendNotification(eq(2L), any(SendNotificationRequest.class)); + } + } + + // ===== SOS EVENTS ===== + + @Test + @DisplayName("GET /api/v1/guardian/sos-events - harus return paginated SOS events") + void sosEvents_shouldReturn200() throws Exception { + try (MockedStatic sh = mockStatic(SecurityHelper.class)) { + sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L); + + Page page = new PageImpl<>(List.of()); + when(sosService.getSosEventsForGuardian(eq(2L), any())).thenReturn(page); + + mockMvc.perform(get("/api/v1/guardian/sos-events")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.message").value("SOS events")); + } + } + + @Test + @DisplayName("PUT /api/v1/guardian/sos/{id}/acknowledge - harus akui SOS") + void acknowledgeSos_shouldReturn200() throws Exception { + try (MockedStatic sh = mockStatic(SecurityHelper.class)) { + sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L); + + SosEventResponse resp = new SosEventResponse(); + when(sosService.acknowledgeSos(2L, 10L)).thenReturn(resp); + + mockMvc.perform(put("/api/v1/guardian/sos/10/acknowledge") + .with(csrf())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.message").value("SOS diakui")); + + verify(sosService).acknowledgeSos(2L, 10L); + } + } + + // ===== AI CONFIG ===== + + @Test + @DisplayName("GET /api/v1/guardian/ai-config - harus return AI config user") + void getAiConfig_shouldReturn200() throws Exception { + try (MockedStatic sh = mockStatic(SecurityHelper.class)) { + sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L); + when(aiConfigService.getConfig(2L)).thenReturn(new AiConfigResponse()); + + mockMvc.perform(get("/api/v1/guardian/ai-config")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.message").value("AI config")); + } + } + + @Test + @DisplayName("PUT /api/v1/guardian/ai-config - harus update AI config dan return response baru") + void updateAiConfig_shouldReturn200() throws Exception { + try (MockedStatic sh = mockStatic(SecurityHelper.class)) { + sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L); + + AiConfigUpdateRequest req = new AiConfigUpdateRequest(); + AiConfigResponse resp = new AiConfigResponse(); + when(aiConfigService.updateConfigByGuardian(eq(2L), any())).thenReturn(resp); + + mockMvc.perform(put("/api/v1/guardian/ai-config") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(req))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.message").value("AI config diperbarui")); + + verify(aiConfigService).updateConfigByGuardian(eq(2L), any(AiConfigUpdateRequest.class)); + } + } + + // ===== VOICE COMMANDS ===== + + @Test + @DisplayName("GET /api/v1/guardian/voice-commands - harus return voice commands user") + void getVoiceCommands_shouldReturn200() throws Exception { + try (MockedStatic sh = mockStatic(SecurityHelper.class)) { + sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L); + when(voiceCommandService.getAll(2L)).thenReturn(List.of()); + + mockMvc.perform(get("/api/v1/guardian/voice-commands")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.message").value("Voice commands")); + } + } + + @Test + @DisplayName("PUT /api/v1/guardian/voice-commands - harus update voice command") + void updateVoiceCommand_shouldReturn200() throws Exception { + try (MockedStatic sh = mockStatic(SecurityHelper.class)) { + sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L); + + VoiceCommandUpdateRequest req = new VoiceCommandUpdateRequest(); + VoiceCommandResponse resp = new VoiceCommandResponse(); + when(voiceCommandService.updateByGuardian(eq(2L), any())).thenReturn(resp); + + mockMvc.perform(put("/api/v1/guardian/voice-commands") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(req))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.message").value("Voice command diperbarui")); + } + } + + // ===== SHORTCUTS ===== + + @Test + @DisplayName("GET /api/v1/guardian/shortcuts - harus return hardware shortcuts") + void getShortcuts_shouldReturn200() throws Exception { + try (MockedStatic sh = mockStatic(SecurityHelper.class)) { + sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L); + when(hardwareShortcutService.getAll(2L)).thenReturn(List.of()); + + mockMvc.perform(get("/api/v1/guardian/shortcuts")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.message").value("Hardware shortcuts")); + } + } + + // ===== GEOFENCE ===== + + @Test + @DisplayName("GET /api/v1/guardian/geofence - harus return konfigurasi geofence") + void getGeofence_shouldReturn200() throws Exception { + try (MockedStatic sh = mockStatic(SecurityHelper.class)) { + sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L); + when(geofenceService.getConfig(2L)).thenReturn(new GeofenceResponse()); + + mockMvc.perform(get("/api/v1/guardian/geofence")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.message").value("Geofence config")); + } + } + + @Test + @DisplayName("PUT /api/v1/guardian/geofence - harus update geofence config") + void updateGeofence_shouldReturn200() throws Exception { + try (MockedStatic sh = mockStatic(SecurityHelper.class)) { + sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L); + + GeofenceConfigRequest req = new GeofenceConfigRequest(); + GeofenceResponse resp = new GeofenceResponse(); + when(geofenceService.updateConfig(eq(2L), any())).thenReturn(resp); + + mockMvc.perform(put("/api/v1/guardian/geofence") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(req))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.message").value("Geofence diperbarui")); + + verify(geofenceService).updateConfig(eq(2L), any(GeofenceConfigRequest.class)); + } + } + + // ===== USER SETTINGS (dikelola Guardian) ===== + + @Test + @DisplayName("GET /api/v1/guardian/user-settings - harus return settings user") + void getUserSettings_shouldReturn200() throws Exception { + try (MockedStatic sh = mockStatic(SecurityHelper.class)) { + sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L); + when(userSettingsService.getSettings(2L)).thenReturn(new UserSettingsResponse()); + + mockMvc.perform(get("/api/v1/guardian/user-settings")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.message").value("User settings")); + } + } + + @Test + @DisplayName("PUT /api/v1/guardian/user-settings - harus update user settings") + void updateUserSettings_shouldReturn200() throws Exception { + try (MockedStatic sh = mockStatic(SecurityHelper.class)) { + sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L); + + UserSettingsUpdateRequest req = new UserSettingsUpdateRequest(); + UserSettingsResponse resp = new UserSettingsResponse(); + when(userSettingsService.updateSettings(eq(2L), any())).thenReturn(resp); + + mockMvc.perform(put("/api/v1/guardian/user-settings") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(req))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.message").value("User settings diperbarui")); + } + } +}