test: add LocationServiceTest unit test

This commit is contained in:
5803024019 2026-05-15 22:00:26 +07:00
parent 2966eda8c7
commit d428c8fc35

View File

@ -0,0 +1,259 @@
package com.walkguide.service;
import com.walkguide.dto.request.LocationUpdateRequest;
import com.walkguide.dto.response.LocationResponse;
import com.walkguide.entity.GeofenceConfig;
import com.walkguide.entity.LocationHistory;
import com.walkguide.entity.PairingRelation;
import com.walkguide.entity.User;
import com.walkguide.enums.PairingStatus;
import com.walkguide.repository.*;
import com.walkguide.websocket.LocationBroadcaster;
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 org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import java.time.LocalDateTime;
import java.util.List;
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("LocationService Unit Tests")
class LocationServiceTest {
@Mock LocationHistoryRepository locationHistoryRepository;
@Mock GeofenceConfigRepository geofenceConfigRepository;
@Mock PairingRelationRepository pairingRelationRepository;
@Mock UserRepository userRepository;
@Mock ActivityLogService activityLogService;
@Mock FcmService fcmService;
@Mock LocationBroadcaster locationBroadcaster;
@InjectMocks LocationService locationService;
private User guardian;
private User user;
private PairingRelation activePairing;
private LocationHistory sampleLocation;
@BeforeEach
void setUp() {
guardian = User.builder()
.id(1L).email("guardian@test.com").displayName("Guardian Test")
.fcmToken("guardian-fcm-token").build();
user = User.builder()
.id(2L).email("user@test.com").displayName("User Test")
.fcmToken("user-fcm-token").build();
activePairing = PairingRelation.builder()
.id(1L).guardian(guardian).user(user).status(PairingStatus.ACTIVE).build();
sampleLocation = LocationHistory.builder()
.id(100L).userId(2L).lat(-7.257).lng(112.752)
.accuracy(10.0).speed(1.5).heading(90.0)
.createdAt(LocalDateTime.now()).build();
}
// ===== updateLocation TESTS =====
@Test
@DisplayName("updateLocation - harus simpan lokasi dan broadcast via WebSocket")
void updateLocation_shouldSaveAndBroadcast() {
LocationUpdateRequest req = new LocationUpdateRequest();
req.setLat(-7.257);
req.setLng(112.752);
req.setAccuracy(10.0);
req.setSpeed(1.5);
req.setHeading(90.0);
when(locationHistoryRepository.save(any(LocationHistory.class))).thenReturn(sampleLocation);
when(geofenceConfigRepository.findByUserId(2L)).thenReturn(Optional.empty());
LocationResponse result = locationService.updateLocation(2L, req);
assertThat(result).isNotNull();
assertThat(result.getLat()).isEqualTo(-7.257);
assertThat(result.getLng()).isEqualTo(112.752);
verify(locationHistoryRepository).save(any(LocationHistory.class));
verify(locationBroadcaster).broadcastLocation(eq(2L), any(LocationResponse.class));
}
@Test
@DisplayName("updateLocation - user dalam geofence: tidak boleh kirim FCM ke guardian")
void updateLocation_insideGeofence_shouldNotTriggerFcm() {
LocationUpdateRequest req = new LocationUpdateRequest();
req.setLat(-7.257); // tepat di center
req.setLng(112.752);
GeofenceConfig cfg = GeofenceConfig.builder()
.userId(2L).centerLat(-7.257).centerLng(112.752)
.radiusMeters(500.0).enabled(true).build();
when(locationHistoryRepository.save(any())).thenReturn(sampleLocation);
when(geofenceConfigRepository.findByUserId(2L)).thenReturn(Optional.of(cfg));
locationService.updateLocation(2L, req);
verify(fcmService, never()).sendHighPriority(any(), any(), any(), any());
}
@Test
@DisplayName("updateLocation - user keluar geofence: harus kirim FCM ke guardian")
void updateLocation_outsideGeofence_shouldSendFcmToGuardian() {
LocationUpdateRequest req = new LocationUpdateRequest();
req.setLat(-7.500); // jauh dari center
req.setLng(113.000);
GeofenceConfig cfg = GeofenceConfig.builder()
.userId(2L).centerLat(-7.257).centerLng(112.752)
.radiusMeters(100.0).enabled(true).build();
LocationHistory savedLoc = LocationHistory.builder()
.id(101L).userId(2L).lat(-7.500).lng(113.000)
.createdAt(LocalDateTime.now()).build();
when(locationHistoryRepository.save(any())).thenReturn(savedLoc);
when(geofenceConfigRepository.findByUserId(2L)).thenReturn(Optional.of(cfg));
when(pairingRelationRepository.findByUser_IdAndStatus(2L, PairingStatus.ACTIVE))
.thenReturn(Optional.of(activePairing));
doNothing().when(activityLogService).createLog(any(), any(), any(), any());
locationService.updateLocation(2L, req);
verify(fcmService).sendHighPriority(
eq("guardian-fcm-token"),
contains("Keluar Area Aman"),
anyString(),
anyMap()
);
}
@Test
@DisplayName("updateLocation - geofence disabled: tidak boleh cek geofence")
void updateLocation_geofenceDisabled_shouldSkipGeofenceCheck() {
LocationUpdateRequest req = new LocationUpdateRequest();
req.setLat(-7.500);
req.setLng(113.000);
GeofenceConfig cfg = GeofenceConfig.builder()
.userId(2L).centerLat(-7.257).centerLng(112.752)
.radiusMeters(100.0).enabled(false).build(); // disabled
when(locationHistoryRepository.save(any())).thenReturn(sampleLocation);
when(geofenceConfigRepository.findByUserId(2L)).thenReturn(Optional.of(cfg));
locationService.updateLocation(2L, req);
verify(fcmService, never()).sendHighPriority(any(), any(), any(), any());
}
// ===== getLastLocation TESTS =====
@Test
@DisplayName("getLastLocation - ada history: harus return Optional berisi lokasi terakhir")
void getLastLocation_historyExists_shouldReturnLocation() {
when(locationHistoryRepository.findTopByUserIdOrderByCreatedAtDesc(2L))
.thenReturn(Optional.of(sampleLocation));
Optional<LocationResponse> result = locationService.getLastLocation(2L);
assertThat(result).isPresent();
assertThat(result.get().getId()).isEqualTo(100L);
assertThat(result.get().getLat()).isEqualTo(-7.257);
}
@Test
@DisplayName("getLastLocation - belum ada history: harus return Optional kosong")
void getLastLocation_noHistory_shouldReturnEmpty() {
when(locationHistoryRepository.findTopByUserIdOrderByCreatedAtDesc(99L))
.thenReturn(Optional.empty());
Optional<LocationResponse> result = locationService.getLastLocation(99L);
assertThat(result).isEmpty();
}
// ===== getLastLocationForGuardian TESTS =====
@Test
@DisplayName("getLastLocationForGuardian - ada pairing aktif: harus return lokasi user yang dipair")
void getLastLocationForGuardian_activePairing_shouldReturnUserLocation() {
when(pairingRelationRepository.findByGuardian_IdAndStatus(1L, PairingStatus.ACTIVE))
.thenReturn(Optional.of(activePairing));
when(locationHistoryRepository.findTopByUserIdOrderByCreatedAtDesc(2L))
.thenReturn(Optional.of(sampleLocation));
Optional<LocationResponse> result = locationService.getLastLocationForGuardian(1L);
assertThat(result).isPresent();
assertThat(result.get().getLat()).isEqualTo(-7.257);
}
@Test
@DisplayName("getLastLocationForGuardian - tidak ada pairing aktif: harus return Optional kosong")
void getLastLocationForGuardian_noPairing_shouldReturnEmpty() {
when(pairingRelationRepository.findByGuardian_IdAndStatus(1L, PairingStatus.ACTIVE))
.thenReturn(Optional.empty());
Optional<LocationResponse> result = locationService.getLastLocationForGuardian(1L);
assertThat(result).isEmpty();
verify(locationHistoryRepository, never()).findTopByUserIdOrderByCreatedAtDesc(any());
}
// ===== getLocationHistory TESTS =====
@Test
@DisplayName("getLocationHistory - harus return halaman lokasi sesuai pageable")
void getLocationHistory_shouldReturnPagedLocations() {
Pageable pageable = PageRequest.of(0, 10);
Page<LocationHistory> page = new PageImpl<>(List.of(sampleLocation), pageable, 1);
when(locationHistoryRepository.findByUserIdOrderByCreatedAtDesc(2L, pageable)).thenReturn(page);
Page<LocationResponse> result = locationService.getLocationHistory(2L, pageable);
assertThat(result).isNotNull();
assertThat(result.getContent()).hasSize(1);
assertThat(result.getContent().get(0).getLat()).isEqualTo(-7.257);
}
// ===== haversineMeters TESTS =====
@Test
@DisplayName("haversineMeters - titik sama: jarak harus 0")
void haversineMeters_samePoint_shouldReturnZero() {
double dist = LocationService.haversineMeters(-7.257, 112.752, -7.257, 112.752);
assertThat(dist).isEqualTo(0.0);
}
@Test
@DisplayName("haversineMeters - dua titik berbeda: harus menghitung jarak yang masuk akal")
void haversineMeters_differentPoints_shouldReturnPositiveDistance() {
// Surabaya ke Sidoarjo ~ 20km
double dist = LocationService.haversineMeters(-7.257, 112.752, -7.447, 112.718);
assertThat(dist).isGreaterThan(10_000).isLessThan(30_000);
}
@Test
@DisplayName("haversineMeters - jarak simetris: A→B sama dengan B→A")
void haversineMeters_isSymmetric() {
double d1 = LocationService.haversineMeters(-7.257, 112.752, -7.300, 112.800);
double d2 = LocationService.haversineMeters(-7.300, 112.800, -7.257, 112.752);
assertThat(d1).isCloseTo(d2, within(0.001));
}
}