diff --git a/walkguide-backend/demo/src/test/java/com/walkguide/service/HardwareShortcutServiceTest.java b/walkguide-backend/demo/src/test/java/com/walkguide/service/HardwareShortcutServiceTest.java new file mode 100644 index 0000000..637081d --- /dev/null +++ b/walkguide-backend/demo/src/test/java/com/walkguide/service/HardwareShortcutServiceTest.java @@ -0,0 +1,186 @@ +package com.walkguide.service; + +import com.walkguide.dto.request.HardwareShortcutUpdateRequest; +import com.walkguide.dto.response.HardwareShortcutResponse; +import com.walkguide.entity.HardwareShortcut; +import com.walkguide.enums.HardwareShortcutKey; +import com.walkguide.exception.ResourceNotFoundException; +import com.walkguide.repository.HardwareShortcutRepository; +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.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +@DisplayName("HardwareShortcutService Unit Tests") +class HardwareShortcutServiceTest { + + @Mock + HardwareShortcutRepository hardwareShortcutRepository; + + @InjectMocks + HardwareShortcutService hardwareShortcutService; + + private HardwareShortcut shortcutCallGuardian; + private HardwareShortcut shortcutSendSos; + + @BeforeEach + void setUp() { + shortcutCallGuardian = HardwareShortcut.builder() + .id(1L) + .userId(10L) + .shortcutKey(HardwareShortcutKey.CALL_GUARDIAN) + .buttonName("Volume Up") + .buttonCode(24) + .enabled(true) + .build(); + + shortcutSendSos = HardwareShortcut.builder() + .id(2L) + .userId(10L) + .shortcutKey(HardwareShortcutKey.SEND_SOS) + .buttonName("Volume Down") + .buttonCode(25) + .enabled(false) + .build(); + } + + // ===== getAll TESTS ===== + + @Test + @DisplayName("getAll - harus return semua shortcut milik user sebagai list response") + void getAll_shouldReturnAllShortcutsForUser() { + when(hardwareShortcutRepository.findByUserId(10L)) + .thenReturn(List.of(shortcutCallGuardian, shortcutSendSos)); + + List result = hardwareShortcutService.getAll(10L); + + assertThat(result).hasSize(2); + assertThat(result.get(0).getShortcutKey()).isEqualTo("CALL_GUARDIAN"); + assertThat(result.get(0).getButtonName()).isEqualTo("Volume Up"); + assertThat(result.get(0).getButtonCode()).isEqualTo(24); + assertThat(result.get(0).getEnabled()).isTrue(); + + assertThat(result.get(1).getShortcutKey()).isEqualTo("SEND_SOS"); + assertThat(result.get(1).getEnabled()).isFalse(); + } + + @Test + @DisplayName("getAll - harus return list kosong jika user tidak punya shortcut") + void getAll_emptyList_whenUserHasNoShortcuts() { + when(hardwareShortcutRepository.findByUserId(99L)).thenReturn(List.of()); + + List result = hardwareShortcutService.getAll(99L); + + assertThat(result).isEmpty(); + } + + // ===== update TESTS ===== + + @Test + @DisplayName("update - harus update buttonName, buttonCode, dan enabled jika semua field diisi") + void update_allFields_shouldUpdateAllFields() { + HardwareShortcutUpdateRequest req = new HardwareShortcutUpdateRequest(); + req.setShortcutKey("CALL_GUARDIAN"); + req.setButtonName("Power Button"); + req.setButtonCode(26); + req.setEnabled(false); + + when(hardwareShortcutRepository.findByUserId(10L)) + .thenReturn(List.of(shortcutCallGuardian, shortcutSendSos)); + when(hardwareShortcutRepository.save(any(HardwareShortcut.class))) + .thenAnswer(inv -> inv.getArgument(0)); + + HardwareShortcutResponse result = hardwareShortcutService.update(10L, req); + + assertThat(result.getButtonName()).isEqualTo("Power Button"); + assertThat(result.getButtonCode()).isEqualTo(26); + assertThat(result.getEnabled()).isFalse(); + assertThat(result.getShortcutKey()).isEqualTo("CALL_GUARDIAN"); + } + + @Test + @DisplayName("update - field null tidak boleh mengubah nilai yang sudah ada") + void update_nullFields_shouldNotOverrideExistingValues() { + HardwareShortcutUpdateRequest req = new HardwareShortcutUpdateRequest(); + req.setShortcutKey("CALL_GUARDIAN"); + req.setButtonName(null); // tidak diubah + req.setButtonCode(null); // tidak diubah + req.setEnabled(null); // tidak diubah + + when(hardwareShortcutRepository.findByUserId(10L)) + .thenReturn(List.of(shortcutCallGuardian)); + when(hardwareShortcutRepository.save(any(HardwareShortcut.class))) + .thenAnswer(inv -> inv.getArgument(0)); + + HardwareShortcutResponse result = hardwareShortcutService.update(10L, req); + + // Nilai lama harus tetap + assertThat(result.getButtonName()).isEqualTo("Volume Up"); + assertThat(result.getButtonCode()).isEqualTo(24); + assertThat(result.getEnabled()).isTrue(); + } + + @Test + @DisplayName("update - shortcutKey tidak ditemukan harus throw ResourceNotFoundException") + void update_shortcutKeyNotFound_shouldThrow() { + HardwareShortcutUpdateRequest req = new HardwareShortcutUpdateRequest(); + req.setShortcutKey("START_WALKGUIDE"); // tidak ada di list user + + when(hardwareShortcutRepository.findByUserId(10L)) + .thenReturn(List.of(shortcutCallGuardian)); // hanya CALL_GUARDIAN + + assertThatThrownBy(() -> hardwareShortcutService.update(10L, req)) + .isInstanceOf(ResourceNotFoundException.class) + .hasMessageContaining("Shortcut tidak ditemukan"); + + verify(hardwareShortcutRepository, never()).save(any()); + } + + @Test + @DisplayName("update - harus memanggil repository.save dengan entity yang sudah dimodifikasi") + void update_shouldPersistChangesToRepository() { + HardwareShortcutUpdateRequest req = new HardwareShortcutUpdateRequest(); + req.setShortcutKey("SEND_SOS"); + req.setEnabled(true); + req.setButtonName("Camera Button"); + + when(hardwareShortcutRepository.findByUserId(10L)) + .thenReturn(List.of(shortcutCallGuardian, shortcutSendSos)); + when(hardwareShortcutRepository.save(any(HardwareShortcut.class))) + .thenAnswer(inv -> inv.getArgument(0)); + + hardwareShortcutService.update(10L, req); + + ArgumentCaptor captor = ArgumentCaptor.forClass(HardwareShortcut.class); + verify(hardwareShortcutRepository).save(captor.capture()); + + HardwareShortcut saved = captor.getValue(); + assertThat(saved.getShortcutKey()).isEqualTo(HardwareShortcutKey.SEND_SOS); + assertThat(saved.getEnabled()).isTrue(); + assertThat(saved.getButtonName()).isEqualTo("Camera Button"); + } + + @Test + @DisplayName("update - shortcutKey enum invalid harus throw IllegalArgumentException") + void update_invalidEnumKey_shouldThrow() { + HardwareShortcutUpdateRequest req = new HardwareShortcutUpdateRequest(); + req.setShortcutKey("INVALID_KEY_XYZ"); + + when(hardwareShortcutRepository.findByUserId(10L)) + .thenReturn(List.of(shortcutCallGuardian)); + + assertThatThrownBy(() -> hardwareShortcutService.update(10L, req)) + .isInstanceOf(IllegalArgumentException.class); + } +} \ No newline at end of file