package com.walkguide.controller; import com.fasterxml.jackson.databind.ObjectMapper; import com.walkguide.dto.request.InviteUserRequest; import com.walkguide.dto.request.PairingResponseRequest; import com.walkguide.dto.response.PairingStatusResponse; import com.walkguide.security.SecurityHelper; import com.walkguide.service.PairingService; 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.AutoConfigureMockMvc; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import com.walkguide.security.JwtAuthFilter; import org.springframework.http.MediaType; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.web.servlet.MockMvc; import java.util.Collection; import java.util.List; 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.*; @AutoConfigureMockMvc(addFilters = false) @WebMvcTest(PairingController.class) @DisplayName("PairingController Unit Tests") class PairingControllerTest { @MockBean private JwtAuthFilter jwtAuthFilter; @Autowired private MockMvc mockMvc; @Autowired private ObjectMapper objectMapper; @MockBean private PairingService pairingService; private PairingStatusResponse buildPairingStatus(String status) { return PairingStatusResponse.builder() .pairingId(1L) .status(status) .pairedWithName("Test Partner") .pairedWithEmail("partner@test.com") .uniqueUserId("ABC123") .build(); } // ===== INVITE ===== @Test @WithMockUser(username = "2", roles = "GUARDIAN") @DisplayName("POST /api/v1/shared/pairing/invite - Guardian undang User harus return 200") void invite_asGuardian_shouldReturn200() throws Exception { try (MockedStatic sh = mockStatic(SecurityHelper.class)) { sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L); InviteUserRequest req = new InviteUserRequest(); req.setUniqueUserId("ABC123DEF456"); PairingStatusResponse resp = buildPairingStatus("PENDING"); when(pairingService.inviteUser(2L, "ABC123DEF456")).thenReturn(resp); mockMvc.perform(post("/api/v1/shared/pairing/invite") .with(csrf()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(req))) .andExpect(status().isOk()) .andExpect(jsonPath("$.success").value(true)) .andExpect(jsonPath("$.message").value("Undangan dikirim ke user")) .andExpect(jsonPath("$.data.status").value("PENDING")); verify(pairingService).inviteUser(2L, "ABC123DEF456"); } } @Test @WithMockUser(username = "2", roles = "GUARDIAN") @DisplayName("POST /api/v1/shared/pairing/invite - uniqueUserId tidak ada harus return 500") void invite_userNotFound_shouldReturn500() throws Exception { try (MockedStatic sh = mockStatic(SecurityHelper.class)) { sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L); InviteUserRequest req = new InviteUserRequest(); req.setPairingCode("BAD99999"); when(pairingService.inviteUser(2L, "BAD99999")) .thenThrow(new RuntimeException("User dengan ID tersebut tidak ditemukan")); mockMvc.perform(post("/api/v1/shared/pairing/invite") .with(csrf()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(req))) .andExpect(status().isInternalServerError()); } } // ===== RESPOND ===== @Test @WithMockUser(username = "1", roles = "USER") @DisplayName("POST /api/v1/shared/pairing/respond - User terima pairing harus return 200") void respond_accept_shouldReturn200WithAcceptMessage() throws Exception { try (MockedStatic sh = mockStatic(SecurityHelper.class)) { sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L); PairingResponseRequest req = new PairingResponseRequest(); req.setPairingId(5L); req.setAccept(true); PairingStatusResponse resp = buildPairingStatus("ACTIVE"); when(pairingService.respondToPairing(1L, 5L, true)).thenReturn(resp); mockMvc.perform(post("/api/v1/shared/pairing/respond") .with(csrf()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(req))) .andExpect(status().isOk()) .andExpect(jsonPath("$.success").value(true)) .andExpect(jsonPath("$.message").value("Pairing diterima")) .andExpect(jsonPath("$.data.status").value("ACTIVE")); verify(pairingService).respondToPairing(1L, 5L, true); } } @Test @WithMockUser(username = "1", roles = "USER") @DisplayName("POST /api/v1/shared/pairing/respond - User tolak pairing harus return 200 pesan ditolak") void respond_reject_shouldReturn200WithRejectMessage() throws Exception { try (MockedStatic sh = mockStatic(SecurityHelper.class)) { sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L); PairingResponseRequest req = new PairingResponseRequest(); req.setPairingId(5L); req.setAccept(false); PairingStatusResponse resp = buildPairingStatus("REJECTED"); when(pairingService.respondToPairing(1L, 5L, false)).thenReturn(resp); mockMvc.perform(post("/api/v1/shared/pairing/respond") .with(csrf()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(req))) .andExpect(status().isOk()) .andExpect(jsonPath("$.message").value("Pairing ditolak")) .andExpect(jsonPath("$.data.status").value("REJECTED")); verify(pairingService).respondToPairing(1L, 5L, false); } } // ===== UNPAIR ===== @Test @WithMockUser(username = "1", roles = "USER") @DisplayName("DELETE /api/v1/shared/pairing/unpair - harus akhiri pairing dan return 200") void unpair_shouldReturn200() throws Exception { try (MockedStatic sh = mockStatic(SecurityHelper.class)) { sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L); doNothing().when(pairingService).unpair(1L); mockMvc.perform(delete("/api/v1/shared/pairing/unpair") .with(csrf())) .andExpect(status().isOk()) .andExpect(jsonPath("$.success").value(true)) .andExpect(jsonPath("$.message").value("Pairing diakhiri")); verify(pairingService).unpair(1L); } } @Test @WithMockUser(username = "2", roles = "GUARDIAN") @DisplayName("DELETE /api/v1/shared/pairing/unpair - Guardian juga bisa unpair") void unpair_asGuardian_shouldReturn200() throws Exception { try (MockedStatic sh = mockStatic(SecurityHelper.class)) { sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L); doNothing().when(pairingService).unpair(2L); mockMvc.perform(delete("/api/v1/shared/pairing/unpair") .with(csrf())) .andExpect(status().isOk()) .andExpect(jsonPath("$.message").value("Pairing diakhiri")); verify(pairingService).unpair(2L); } } @Test @WithMockUser(username = "1", roles = "USER") @DisplayName("DELETE /api/v1/shared/pairing/unpair - service throw harus return 500") void unpair_serviceThrows_shouldReturn500() throws Exception { try (MockedStatic sh = mockStatic(SecurityHelper.class)) { sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L); doThrow(new RuntimeException("Tidak ada pairing aktif")).when(pairingService).unpair(1L); mockMvc.perform(delete("/api/v1/shared/pairing/unpair") .with(csrf())) .andExpect(status().isInternalServerError()); } } // ===== STATUS ===== @Test @WithMockUser(username = "1", roles = "USER") @DisplayName("GET /api/v1/shared/pairing/status - User harus return status pairing") void status_asUser_shouldReturn200() throws Exception { try (MockedStatic sh = mockStatic(SecurityHelper.class)) { sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L); sh.when(SecurityHelper::getCurrentUserRole).thenReturn("ROLE_USER"); PairingStatusResponse resp = buildPairingStatus("ACTIVE"); when(pairingService.getStatus(eq(1L), eq("ROLE_USER"))).thenReturn(resp); mockMvc.perform(get("/api/v1/shared/pairing/status")) .andExpect(status().isOk()) .andExpect(jsonPath("$.success").value(true)) .andExpect(jsonPath("$.message").value("Status pairing")) .andExpect(jsonPath("$.data.status").value("ACTIVE")) .andExpect(jsonPath("$.data.pairingId").value(1)); } } @Test @WithMockUser(username = "2", roles = "GUARDIAN") @DisplayName("GET /api/v1/shared/pairing/status - Guardian harus return status pairing dengan role GUARDIAN") void status_asGuardian_shouldReturn200() throws Exception { try (MockedStatic sh = mockStatic(SecurityHelper.class)) { sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L); sh.when(SecurityHelper::getCurrentUserRole).thenReturn("ROLE_GUARDIAN"); PairingStatusResponse resp = buildPairingStatus("ACTIVE"); when(pairingService.getStatus(eq(2L), eq("ROLE_GUARDIAN"))).thenReturn(resp); mockMvc.perform(get("/api/v1/shared/pairing/status")) .andExpect(status().isOk()) .andExpect(jsonPath("$.message").value("Status pairing")); } } @Test @WithMockUser(username = "1", roles = "USER") @DisplayName("GET /api/v1/shared/pairing/status - belum ada pairing harus return status NONE") void status_noPairing_shouldReturnNoneStatus() throws Exception { try (MockedStatic sh = mockStatic(SecurityHelper.class)) { sh.when(SecurityHelper::getCurrentUserId).thenReturn(1L); sh.when(SecurityHelper::getCurrentUserRole).thenReturn("ROLE_USER"); PairingStatusResponse resp = PairingStatusResponse.builder() .status("NONE") .build(); when(pairingService.getStatus(eq(1L), anyString())).thenReturn(resp); mockMvc.perform(get("/api/v1/shared/pairing/status")) .andExpect(status().isOk()) .andExpect(jsonPath("$.data.status").value("NONE")); } } }