test: add VoiceCommandServiceTest unit test
This commit is contained in:
parent
8f265c8efa
commit
b7a9079930
@ -0,0 +1,394 @@
|
||||
package com.walkguide.service;
|
||||
|
||||
import com.walkguide.dto.request.VoiceCommandUpdateRequest;
|
||||
import com.walkguide.dto.response.VoiceCommandResponse;
|
||||
import com.walkguide.entity.PairingRelation;
|
||||
import com.walkguide.entity.User;
|
||||
import com.walkguide.entity.VoiceCommandConfig;
|
||||
import com.walkguide.enums.PairingStatus;
|
||||
import com.walkguide.enums.VoiceCommandKey;
|
||||
import com.walkguide.exception.PairingException;
|
||||
import com.walkguide.exception.ResourceNotFoundException;
|
||||
import com.walkguide.repository.PairingRelationRepository;
|
||||
import com.walkguide.repository.VoiceCommandConfigRepository;
|
||||
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 java.util.Optional;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@DisplayName("VoiceCommandService Unit Tests")
|
||||
class VoiceCommandServiceTest {
|
||||
|
||||
@Mock
|
||||
VoiceCommandConfigRepository voiceCommandConfigRepository;
|
||||
|
||||
@Mock
|
||||
PairingRelationRepository pairingRelationRepository;
|
||||
|
||||
@Mock
|
||||
FcmService fcmService;
|
||||
|
||||
@InjectMocks
|
||||
VoiceCommandService voiceCommandService;
|
||||
|
||||
private User guardianUser;
|
||||
private User blindUser;
|
||||
private PairingRelation activePairing;
|
||||
private VoiceCommandConfig vcOpenWalkguide;
|
||||
private VoiceCommandConfig vcCallGuardian;
|
||||
private VoiceCommandConfig vcSendSos;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
guardianUser = User.builder()
|
||||
.id(1L)
|
||||
.email("guardian@test.com")
|
||||
.role("ROLE_GUARDIAN")
|
||||
.displayName("Guardian Test")
|
||||
.fcmToken("guardian-fcm-token")
|
||||
.build();
|
||||
|
||||
blindUser = User.builder()
|
||||
.id(2L)
|
||||
.email("user@test.com")
|
||||
.role("ROLE_USER")
|
||||
.displayName("User Test")
|
||||
.fcmToken("user-fcm-token-xyz")
|
||||
.build();
|
||||
|
||||
activePairing = PairingRelation.builder()
|
||||
.id(10L)
|
||||
.guardian(guardianUser)
|
||||
.user(blindUser)
|
||||
.status(PairingStatus.ACTIVE)
|
||||
.build();
|
||||
|
||||
vcOpenWalkguide = VoiceCommandConfig.builder()
|
||||
.id(100L)
|
||||
.userId(2L)
|
||||
.commandKey(VoiceCommandKey.OPEN_WALKGUIDE)
|
||||
.triggerPhrase("buka walkguide")
|
||||
.enabled(true)
|
||||
.build();
|
||||
|
||||
vcCallGuardian = VoiceCommandConfig.builder()
|
||||
.id(101L)
|
||||
.userId(2L)
|
||||
.commandKey(VoiceCommandKey.CALL_GUARDIAN)
|
||||
.triggerPhrase("telepon guardian")
|
||||
.enabled(true)
|
||||
.build();
|
||||
|
||||
vcSendSos = VoiceCommandConfig.builder()
|
||||
.id(102L)
|
||||
.userId(2L)
|
||||
.commandKey(VoiceCommandKey.SEND_SOS)
|
||||
.triggerPhrase("kirim sos")
|
||||
.enabled(false)
|
||||
.build();
|
||||
}
|
||||
|
||||
// ===== getAll TESTS =====
|
||||
|
||||
@Test
|
||||
@DisplayName("getAll - harus return semua voice command milik user sebagai list response")
|
||||
void getAll_shouldReturnAllVoiceCommandsForUser() {
|
||||
when(voiceCommandConfigRepository.findByUserId(2L))
|
||||
.thenReturn(List.of(vcOpenWalkguide, vcCallGuardian, vcSendSos));
|
||||
|
||||
List<VoiceCommandResponse> result = voiceCommandService.getAll(2L);
|
||||
|
||||
assertThat(result).hasSize(3);
|
||||
|
||||
VoiceCommandResponse first = result.get(0);
|
||||
assertThat(first.getId()).isEqualTo(100L);
|
||||
assertThat(first.getCommandKey()).isEqualTo("OPEN_WALKGUIDE");
|
||||
assertThat(first.getTriggerPhrase()).isEqualTo("buka walkguide");
|
||||
assertThat(first.getEnabled()).isTrue();
|
||||
assertThat(first.getDescription()).isEqualTo("Buka menu WalkGuide");
|
||||
|
||||
VoiceCommandResponse second = result.get(1);
|
||||
assertThat(second.getCommandKey()).isEqualTo("CALL_GUARDIAN");
|
||||
assertThat(second.getDescription()).isEqualTo("Telepon Guardian");
|
||||
|
||||
VoiceCommandResponse third = result.get(2);
|
||||
assertThat(third.getCommandKey()).isEqualTo("SEND_SOS");
|
||||
assertThat(third.getEnabled()).isFalse();
|
||||
assertThat(third.getDescription()).isEqualTo("Kirim sinyal darurat SOS");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getAll - harus return list kosong jika user tidak punya voice command")
|
||||
void getAll_emptyList_whenUserHasNoVoiceCommands() {
|
||||
when(voiceCommandConfigRepository.findByUserId(99L)).thenReturn(List.of());
|
||||
|
||||
List<VoiceCommandResponse> result = voiceCommandService.getAll(99L);
|
||||
|
||||
assertThat(result).isEmpty();
|
||||
verify(voiceCommandConfigRepository).findByUserId(99L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getAll - description harus ada untuk semua VoiceCommandKey yang terdefinisi")
|
||||
void getAll_shouldProvideDescriptionForAllDefinedKeys() {
|
||||
// Buat config dengan setiap key yang ada
|
||||
List<VoiceCommandConfig> allConfigs = List.of(
|
||||
buildVc(1L, 2L, VoiceCommandKey.OPEN_WALKGUIDE, "cmd1"),
|
||||
buildVc(2L, 2L, VoiceCommandKey.START_WALKGUIDE, "cmd2"),
|
||||
buildVc(3L, 2L, VoiceCommandKey.STOP_WALKGUIDE, "cmd3"),
|
||||
buildVc(4L, 2L, VoiceCommandKey.CALL_GUARDIAN, "cmd4"),
|
||||
buildVc(5L, 2L, VoiceCommandKey.OPEN_NOTIFICATION, "cmd5"),
|
||||
buildVc(6L, 2L, VoiceCommandKey.READ_ALL_NOTIF, "cmd6"),
|
||||
buildVc(7L, 2L, VoiceCommandKey.OPEN_SOS, "cmd7"),
|
||||
buildVc(8L, 2L, VoiceCommandKey.SEND_SOS, "cmd8"),
|
||||
buildVc(9L, 2L, VoiceCommandKey.WHERE_AM_I, "cmd9"),
|
||||
buildVc(10L, 2L, VoiceCommandKey.OPEN_ACTIVITY, "cmd10"),
|
||||
buildVc(11L, 2L, VoiceCommandKey.OPEN_NAVIGATION, "cmd11"),
|
||||
buildVc(12L, 2L, VoiceCommandKey.OPEN_SETTINGS, "cmd12"),
|
||||
buildVc(13L, 2L, VoiceCommandKey.REPEAT_LAST, "cmd13"),
|
||||
buildVc(14L, 2L, VoiceCommandKey.STOP_TTS, "cmd14")
|
||||
);
|
||||
|
||||
when(voiceCommandConfigRepository.findByUserId(2L)).thenReturn(allConfigs);
|
||||
|
||||
List<VoiceCommandResponse> result = voiceCommandService.getAll(2L);
|
||||
|
||||
assertThat(result).hasSize(14);
|
||||
// Semua description tidak boleh kosong atau null
|
||||
result.forEach(r -> assertThat(r.getDescription())
|
||||
.as("Description untuk key %s tidak boleh kosong", r.getCommandKey())
|
||||
.isNotNull()
|
||||
.isNotBlank());
|
||||
}
|
||||
|
||||
// ===== updateByGuardian TESTS =====
|
||||
|
||||
@Test
|
||||
@DisplayName("updateByGuardian - harus update triggerPhrase dan enabled jika semua field diisi")
|
||||
void updateByGuardian_allFields_shouldUpdateAll() {
|
||||
VoiceCommandUpdateRequest req = new VoiceCommandUpdateRequest();
|
||||
req.setCommandKey("OPEN_WALKGUIDE");
|
||||
req.setTriggerPhrase("halo walkguide");
|
||||
req.setEnabled(false);
|
||||
|
||||
when(pairingRelationRepository.findByGuardian_IdAndStatus(1L, PairingStatus.ACTIVE))
|
||||
.thenReturn(Optional.of(activePairing));
|
||||
when(voiceCommandConfigRepository.findByUserIdAndCommandKey(2L, VoiceCommandKey.OPEN_WALKGUIDE))
|
||||
.thenReturn(Optional.of(vcOpenWalkguide));
|
||||
when(voiceCommandConfigRepository.save(any(VoiceCommandConfig.class)))
|
||||
.thenAnswer(inv -> inv.getArgument(0));
|
||||
|
||||
VoiceCommandResponse result = voiceCommandService.updateByGuardian(1L, req);
|
||||
|
||||
assertThat(result.getCommandKey()).isEqualTo("OPEN_WALKGUIDE");
|
||||
assertThat(result.getTriggerPhrase()).isEqualTo("halo walkguide");
|
||||
assertThat(result.getEnabled()).isFalse();
|
||||
assertThat(result.getDescription()).isEqualTo("Buka menu WalkGuide");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("updateByGuardian - triggerPhrase null tidak boleh mengubah nilai yang sudah ada")
|
||||
void updateByGuardian_nullTriggerPhrase_shouldKeepExistingPhrase() {
|
||||
VoiceCommandUpdateRequest req = new VoiceCommandUpdateRequest();
|
||||
req.setCommandKey("CALL_GUARDIAN");
|
||||
req.setTriggerPhrase(null);
|
||||
req.setEnabled(false);
|
||||
|
||||
when(pairingRelationRepository.findByGuardian_IdAndStatus(1L, PairingStatus.ACTIVE))
|
||||
.thenReturn(Optional.of(activePairing));
|
||||
when(voiceCommandConfigRepository.findByUserIdAndCommandKey(2L, VoiceCommandKey.CALL_GUARDIAN))
|
||||
.thenReturn(Optional.of(vcCallGuardian));
|
||||
when(voiceCommandConfigRepository.save(any(VoiceCommandConfig.class)))
|
||||
.thenAnswer(inv -> inv.getArgument(0));
|
||||
|
||||
VoiceCommandResponse result = voiceCommandService.updateByGuardian(1L, req);
|
||||
|
||||
// triggerPhrase tidak berubah
|
||||
assertThat(result.getTriggerPhrase()).isEqualTo("telepon guardian");
|
||||
assertThat(result.getEnabled()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("updateByGuardian - triggerPhrase blank tidak boleh mengubah nilai yang sudah ada")
|
||||
void updateByGuardian_blankTriggerPhrase_shouldKeepExistingPhrase() {
|
||||
VoiceCommandUpdateRequest req = new VoiceCommandUpdateRequest();
|
||||
req.setCommandKey("CALL_GUARDIAN");
|
||||
req.setTriggerPhrase(" "); // blank string
|
||||
req.setEnabled(null);
|
||||
|
||||
when(pairingRelationRepository.findByGuardian_IdAndStatus(1L, PairingStatus.ACTIVE))
|
||||
.thenReturn(Optional.of(activePairing));
|
||||
when(voiceCommandConfigRepository.findByUserIdAndCommandKey(2L, VoiceCommandKey.CALL_GUARDIAN))
|
||||
.thenReturn(Optional.of(vcCallGuardian));
|
||||
when(voiceCommandConfigRepository.save(any(VoiceCommandConfig.class)))
|
||||
.thenAnswer(inv -> inv.getArgument(0));
|
||||
|
||||
VoiceCommandResponse result = voiceCommandService.updateByGuardian(1L, req);
|
||||
|
||||
assertThat(result.getTriggerPhrase()).isEqualTo("telepon guardian");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("updateByGuardian - enabled null tidak boleh mengubah nilai yang sudah ada")
|
||||
void updateByGuardian_nullEnabled_shouldKeepExistingEnabled() {
|
||||
VoiceCommandUpdateRequest req = new VoiceCommandUpdateRequest();
|
||||
req.setCommandKey("SEND_SOS");
|
||||
req.setTriggerPhrase("darurat sekarang");
|
||||
req.setEnabled(null);
|
||||
|
||||
when(pairingRelationRepository.findByGuardian_IdAndStatus(1L, PairingStatus.ACTIVE))
|
||||
.thenReturn(Optional.of(activePairing));
|
||||
when(voiceCommandConfigRepository.findByUserIdAndCommandKey(2L, VoiceCommandKey.SEND_SOS))
|
||||
.thenReturn(Optional.of(vcSendSos));
|
||||
when(voiceCommandConfigRepository.save(any(VoiceCommandConfig.class)))
|
||||
.thenAnswer(inv -> inv.getArgument(0));
|
||||
|
||||
VoiceCommandResponse result = voiceCommandService.updateByGuardian(1L, req);
|
||||
|
||||
// enabled tetap false (nilai awal vcSendSos)
|
||||
assertThat(result.getEnabled()).isFalse();
|
||||
assertThat(result.getTriggerPhrase()).isEqualTo("darurat sekarang");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("updateByGuardian - guardian tanpa pairing aktif harus throw PairingException")
|
||||
void updateByGuardian_noActivePairing_shouldThrowPairingException() {
|
||||
VoiceCommandUpdateRequest req = new VoiceCommandUpdateRequest();
|
||||
req.setCommandKey("OPEN_WALKGUIDE");
|
||||
|
||||
when(pairingRelationRepository.findByGuardian_IdAndStatus(1L, PairingStatus.ACTIVE))
|
||||
.thenReturn(Optional.empty());
|
||||
|
||||
assertThatThrownBy(() -> voiceCommandService.updateByGuardian(1L, req))
|
||||
.isInstanceOf(PairingException.class)
|
||||
.hasMessageContaining("Tidak ada user yang dipair");
|
||||
|
||||
verify(voiceCommandConfigRepository, never()).findByUserIdAndCommandKey(any(), any());
|
||||
verify(voiceCommandConfigRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("updateByGuardian - commandKey tidak ditemukan harus throw ResourceNotFoundException")
|
||||
void updateByGuardian_voiceCommandNotFound_shouldThrowResourceNotFoundException() {
|
||||
VoiceCommandUpdateRequest req = new VoiceCommandUpdateRequest();
|
||||
req.setCommandKey("WHERE_AM_I");
|
||||
|
||||
when(pairingRelationRepository.findByGuardian_IdAndStatus(1L, PairingStatus.ACTIVE))
|
||||
.thenReturn(Optional.of(activePairing));
|
||||
when(voiceCommandConfigRepository.findByUserIdAndCommandKey(2L, VoiceCommandKey.WHERE_AM_I))
|
||||
.thenReturn(Optional.empty());
|
||||
|
||||
assertThatThrownBy(() -> voiceCommandService.updateByGuardian(1L, req))
|
||||
.isInstanceOf(ResourceNotFoundException.class)
|
||||
.hasMessageContaining("Voice command tidak ditemukan");
|
||||
|
||||
verify(voiceCommandConfigRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("updateByGuardian - commandKey enum invalid harus throw IllegalArgumentException")
|
||||
void updateByGuardian_invalidEnumKey_shouldThrowIllegalArgumentException() {
|
||||
VoiceCommandUpdateRequest req = new VoiceCommandUpdateRequest();
|
||||
req.setCommandKey("INVALID_COMMAND_XYZ");
|
||||
|
||||
when(pairingRelationRepository.findByGuardian_IdAndStatus(1L, PairingStatus.ACTIVE))
|
||||
.thenReturn(Optional.of(activePairing));
|
||||
|
||||
assertThatThrownBy(() -> voiceCommandService.updateByGuardian(1L, req))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
|
||||
verify(voiceCommandConfigRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("updateByGuardian - harus menyimpan entitas yang sudah dimodifikasi ke repository")
|
||||
void updateByGuardian_shouldPersistChangesToRepository() {
|
||||
VoiceCommandUpdateRequest req = new VoiceCommandUpdateRequest();
|
||||
req.setCommandKey("SEND_SOS");
|
||||
req.setTriggerPhrase("minta tolong segera");
|
||||
req.setEnabled(true);
|
||||
|
||||
when(pairingRelationRepository.findByGuardian_IdAndStatus(1L, PairingStatus.ACTIVE))
|
||||
.thenReturn(Optional.of(activePairing));
|
||||
when(voiceCommandConfigRepository.findByUserIdAndCommandKey(2L, VoiceCommandKey.SEND_SOS))
|
||||
.thenReturn(Optional.of(vcSendSos));
|
||||
when(voiceCommandConfigRepository.save(any(VoiceCommandConfig.class)))
|
||||
.thenAnswer(inv -> inv.getArgument(0));
|
||||
|
||||
voiceCommandService.updateByGuardian(1L, req);
|
||||
|
||||
ArgumentCaptor<VoiceCommandConfig> captor = ArgumentCaptor.forClass(VoiceCommandConfig.class);
|
||||
verify(voiceCommandConfigRepository).save(captor.capture());
|
||||
|
||||
VoiceCommandConfig saved = captor.getValue();
|
||||
assertThat(saved.getCommandKey()).isEqualTo(VoiceCommandKey.SEND_SOS);
|
||||
assertThat(saved.getTriggerPhrase()).isEqualTo("minta tolong segera");
|
||||
assertThat(saved.getEnabled()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("updateByGuardian - harus mengirim FCM notification ke user setelah update berhasil")
|
||||
void updateByGuardian_shouldSendFcmNotificationToUser() {
|
||||
VoiceCommandUpdateRequest req = new VoiceCommandUpdateRequest();
|
||||
req.setCommandKey("OPEN_WALKGUIDE");
|
||||
req.setTriggerPhrase("ayo walkguide");
|
||||
req.setEnabled(true);
|
||||
|
||||
when(pairingRelationRepository.findByGuardian_IdAndStatus(1L, PairingStatus.ACTIVE))
|
||||
.thenReturn(Optional.of(activePairing));
|
||||
when(voiceCommandConfigRepository.findByUserIdAndCommandKey(2L, VoiceCommandKey.OPEN_WALKGUIDE))
|
||||
.thenReturn(Optional.of(vcOpenWalkguide));
|
||||
when(voiceCommandConfigRepository.save(any(VoiceCommandConfig.class)))
|
||||
.thenAnswer(inv -> inv.getArgument(0));
|
||||
|
||||
voiceCommandService.updateByGuardian(1L, req);
|
||||
|
||||
verify(fcmService).sendToToken(
|
||||
eq("user-fcm-token-xyz"),
|
||||
eq("Voice Command Diperbarui"),
|
||||
eq("Guardian mengubah perintah suara kamu"),
|
||||
argThat(data -> "SETTINGS_UPDATED".equals(data.get("type"))
|
||||
&& "VOICE_COMMAND".equals(data.get("settingType")))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("updateByGuardian - FCM tidak boleh dipanggil jika update gagal karena pairing tidak ada")
|
||||
void updateByGuardian_noPairing_fcmShouldNotBeCalled() {
|
||||
VoiceCommandUpdateRequest req = new VoiceCommandUpdateRequest();
|
||||
req.setCommandKey("OPEN_WALKGUIDE");
|
||||
|
||||
when(pairingRelationRepository.findByGuardian_IdAndStatus(1L, PairingStatus.ACTIVE))
|
||||
.thenReturn(Optional.empty());
|
||||
|
||||
assertThatThrownBy(() -> voiceCommandService.updateByGuardian(1L, req))
|
||||
.isInstanceOf(PairingException.class);
|
||||
|
||||
verifyNoInteractions(fcmService);
|
||||
}
|
||||
|
||||
// ===== Helper =====
|
||||
|
||||
private VoiceCommandConfig buildVc(Long id, Long userId, VoiceCommandKey key, String phrase) {
|
||||
return VoiceCommandConfig.builder()
|
||||
.id(id)
|
||||
.userId(userId)
|
||||
.commandKey(key)
|
||||
.triggerPhrase(phrase)
|
||||
.enabled(true)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user