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