test: add LocationServiceTest unit test
This commit is contained in:
parent
2966eda8c7
commit
d428c8fc35
@ -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));
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user