diff --git a/walkguide-backend/demo/src/test/java/com/walkguide/service/GeofenceServiceTest.java b/walkguide-backend/demo/src/test/java/com/walkguide/service/GeofenceServiceTest.java new file mode 100644 index 0000000..e67aba4 --- /dev/null +++ b/walkguide-backend/demo/src/test/java/com/walkguide/service/GeofenceServiceTest.java @@ -0,0 +1,184 @@ +package com.walkguide.service; + +import com.walkguide.dto.request.GeofenceConfigRequest; +import com.walkguide.dto.response.GeofenceResponse; +import com.walkguide.entity.GeofenceConfig; +import com.walkguide.entity.PairingRelation; +import com.walkguide.entity.User; +import com.walkguide.enums.PairingStatus; +import com.walkguide.exception.PairingException; +import com.walkguide.repository.GeofenceConfigRepository; +import com.walkguide.repository.PairingRelationRepository; +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.Optional; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +@DisplayName("GeofenceService Unit Tests") +class GeofenceServiceTest { + + @Mock GeofenceConfigRepository geofenceConfigRepository; + @Mock PairingRelationRepository pairingRelationRepository; + + @InjectMocks GeofenceService geofenceService; + + private User guardian; + private User user; + private PairingRelation activePairing; + private GeofenceConfig existingConfig; + + @BeforeEach + void setUp() { + guardian = User.builder().id(1L).email("guardian@test.com").displayName("Guardian Test").build(); + user = User.builder().id(2L).email("user@test.com").displayName("User Test").build(); + + activePairing = PairingRelation.builder() + .id(1L) + .guardian(guardian) + .user(user) + .status(PairingStatus.ACTIVE) + .build(); + + existingConfig = GeofenceConfig.builder() + .id(10L) + .userId(2L) + .guardianId(1L) + .centerLat(-7.257) + .centerLng(112.752) + .radiusMeters(300.0) + .enabled(true) + .build(); + } + + // ===== getConfig TESTS ===== + + @Test + @DisplayName("getConfig - konfigurasi ada: harus return data dari repository") + void getConfig_configExists_shouldReturnExistingConfig() { + when(geofenceConfigRepository.findByUserId(2L)).thenReturn(Optional.of(existingConfig)); + + GeofenceResponse result = geofenceService.getConfig(2L); + + assertThat(result).isNotNull(); + assertThat(result.getId()).isEqualTo(10L); + assertThat(result.getCenterLat()).isEqualTo(-7.257); + assertThat(result.getCenterLng()).isEqualTo(112.752); + assertThat(result.getRadiusMeters()).isEqualTo(300.0); + assertThat(result.getEnabled()).isTrue(); + } + + @Test + @DisplayName("getConfig - konfigurasi tidak ada: harus return default (disabled, radius 500m)") + void getConfig_configNotFound_shouldReturnDefaultResponse() { + when(geofenceConfigRepository.findByUserId(99L)).thenReturn(Optional.empty()); + + GeofenceResponse result = geofenceService.getConfig(99L); + + assertThat(result).isNotNull(); + assertThat(result.getEnabled()).isFalse(); + assertThat(result.getRadiusMeters()).isEqualTo(500.0); + assertThat(result.getId()).isNull(); + } + + // ===== updateConfig TESTS ===== + + @Test + @DisplayName("updateConfig - pairing aktif, config sudah ada: harus update field yang diberikan saja") + void updateConfig_activePairingAndConfigExists_shouldUpdateFields() { + GeofenceConfigRequest req = new GeofenceConfigRequest(); + req.setCenterLat(-7.300); + req.setCenterLng(112.800); + req.setRadiusMeters(400.0); + req.setEnabled(true); + + when(pairingRelationRepository.findByGuardian_IdAndStatus(1L, PairingStatus.ACTIVE)) + .thenReturn(Optional.of(activePairing)); + when(geofenceConfigRepository.findByUserId(2L)).thenReturn(Optional.of(existingConfig)); + when(geofenceConfigRepository.save(any(GeofenceConfig.class))).thenAnswer(inv -> inv.getArgument(0)); + + GeofenceResponse result = geofenceService.updateConfig(1L, req); + + assertThat(result).isNotNull(); + assertThat(result.getCenterLat()).isEqualTo(-7.300); + assertThat(result.getCenterLng()).isEqualTo(112.800); + assertThat(result.getRadiusMeters()).isEqualTo(400.0); + assertThat(result.getEnabled()).isTrue(); + verify(geofenceConfigRepository).save(any(GeofenceConfig.class)); + } + + @Test + @DisplayName("updateConfig - config belum ada: harus buat config baru dengan userId dari pairing") + void updateConfig_configNotExists_shouldCreateNewConfig() { + GeofenceConfigRequest req = new GeofenceConfigRequest(); + req.setCenterLat(-7.100); + req.setCenterLng(112.600); + req.setRadiusMeters(200.0); + req.setEnabled(false); + + when(pairingRelationRepository.findByGuardian_IdAndStatus(1L, PairingStatus.ACTIVE)) + .thenReturn(Optional.of(activePairing)); + when(geofenceConfigRepository.findByUserId(2L)).thenReturn(Optional.empty()); + when(geofenceConfigRepository.save(any(GeofenceConfig.class))).thenAnswer(inv -> { + GeofenceConfig saved = inv.getArgument(0); + saved.setId(20L); + return saved; + }); + + GeofenceResponse result = geofenceService.updateConfig(1L, req); + + ArgumentCaptor captor = ArgumentCaptor.forClass(GeofenceConfig.class); + verify(geofenceConfigRepository).save(captor.capture()); + GeofenceConfig saved = captor.getValue(); + assertThat(saved.getUserId()).isEqualTo(2L); + assertThat(saved.getGuardianId()).isEqualTo(1L); + assertThat(result.getCenterLat()).isEqualTo(-7.100); + assertThat(result.getEnabled()).isFalse(); + } + + @Test + @DisplayName("updateConfig - request field null: harus tidak menimpa nilai yang sudah ada") + void updateConfig_partialRequest_shouldNotOverwriteNullFields() { + GeofenceConfigRequest req = new GeofenceConfigRequest(); + req.setEnabled(false); // hanya ubah enabled, field lain null + + when(pairingRelationRepository.findByGuardian_IdAndStatus(1L, PairingStatus.ACTIVE)) + .thenReturn(Optional.of(activePairing)); + when(geofenceConfigRepository.findByUserId(2L)).thenReturn(Optional.of(existingConfig)); + when(geofenceConfigRepository.save(any(GeofenceConfig.class))).thenAnswer(inv -> inv.getArgument(0)); + + GeofenceResponse result = geofenceService.updateConfig(1L, req); + + // centerLat, centerLng, radiusMeters tidak berubah + assertThat(result.getCenterLat()).isEqualTo(-7.257); + assertThat(result.getCenterLng()).isEqualTo(112.752); + assertThat(result.getRadiusMeters()).isEqualTo(300.0); + assertThat(result.getEnabled()).isFalse(); + } + + @Test + @DisplayName("updateConfig - tidak ada pairing aktif: harus throw PairingException") + void updateConfig_noActivePairing_shouldThrowPairingException() { + GeofenceConfigRequest req = new GeofenceConfigRequest(); + req.setEnabled(true); + + when(pairingRelationRepository.findByGuardian_IdAndStatus(1L, PairingStatus.ACTIVE)) + .thenReturn(Optional.empty()); + + assertThatThrownBy(() -> geofenceService.updateConfig(1L, req)) + .isInstanceOf(PairingException.class) + .hasMessageContaining("Tidak ada user yang dipair"); + + verify(geofenceConfigRepository, never()).save(any()); + } +} \ No newline at end of file