From 2c5626c0f91b3547f92b30b180d979c4607d5cff Mon Sep 17 00:00:00 2001 From: 5803024019 Date: Fri, 15 May 2026 21:46:04 +0700 Subject: [PATCH] test: add ActivityLogServiceTest unit test --- .../service/ActivityLogServiceTest.java | 196 ++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 walkguide-backend/demo/src/test/java/com/walkguide/service/ActivityLogServiceTest.java diff --git a/walkguide-backend/demo/src/test/java/com/walkguide/service/ActivityLogServiceTest.java b/walkguide-backend/demo/src/test/java/com/walkguide/service/ActivityLogServiceTest.java new file mode 100644 index 0000000..4867841 --- /dev/null +++ b/walkguide-backend/demo/src/test/java/com/walkguide/service/ActivityLogServiceTest.java @@ -0,0 +1,196 @@ +package com.walkguide.service; + +import com.walkguide.dto.response.ActivityLogResponse; +import com.walkguide.entity.ActivityLog; +import com.walkguide.entity.PairingRelation; +import com.walkguide.entity.User; +import com.walkguide.enums.ActivityLogType; +import com.walkguide.enums.PairingStatus; +import com.walkguide.exception.ResourceNotFoundException; +import com.walkguide.repository.ActivityLogRepository; +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 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("ActivityLogService Unit Tests") +class ActivityLogServiceTest { + + @Mock ActivityLogRepository activityLogRepository; + @Mock PairingRelationRepository pairingRelationRepository; + + @InjectMocks ActivityLogService activityLogService; + + private User sampleUser; + private User sampleGuardian; + private ActivityLog sampleLog; + + @BeforeEach + void setUp() { + sampleUser = User.builder() + .id(1L).email("user@test.com").role("ROLE_USER") + .displayName("Test User").build(); + + sampleGuardian = User.builder() + .id(2L).email("guardian@test.com").role("ROLE_GUARDIAN") + .displayName("Test Guardian").build(); + + sampleLog = ActivityLog.builder() + .id(10L).user(sampleUser) + .logType(ActivityLogType.LOGIN) + .description("User login") + .metadata(null) + .build(); + } + + // ===== CREATE LOG TESTS ===== + + @Test + @DisplayName("createLog - harus simpan ActivityLog dengan data yang benar") + void createLog_shouldSaveWithCorrectData() { + activityLogService.createLog(sampleUser, ActivityLogType.LOGIN, "User login", null); + + ArgumentCaptor captor = ArgumentCaptor.forClass(ActivityLog.class); + verify(activityLogRepository).save(captor.capture()); + + ActivityLog saved = captor.getValue(); + assertThat(saved.getUser()).isEqualTo(sampleUser); + assertThat(saved.getLogType()).isEqualTo(ActivityLogType.LOGIN); + assertThat(saved.getDescription()).isEqualTo("User login"); + assertThat(saved.getMetadata()).isNull(); + } + + @Test + @DisplayName("createLog - harus simpan ActivityLog dengan metadata") + void createLog_withMetadata_shouldSaveMetadata() { + String meta = "{\"label\":\"person\",\"direction\":\"LEFT\"}"; + activityLogService.createLog(sampleUser, ActivityLogType.OBSTACLE_DETECTED, "Obstacle terdeteksi", meta); + + ArgumentCaptor captor = ArgumentCaptor.forClass(ActivityLog.class); + verify(activityLogRepository).save(captor.capture()); + + assertThat(captor.getValue().getMetadata()).isEqualTo(meta); + assertThat(captor.getValue().getLogType()).isEqualTo(ActivityLogType.OBSTACLE_DETECTED); + } + + @Test + @DisplayName("createLog - berbagai tipe log harus disimpan dengan benar") + void createLog_variousLogTypes_shouldSaveCorrectly() { + ActivityLogType[] types = { + ActivityLogType.LOGOUT, ActivityLogType.SOS_TRIGGERED, + ActivityLogType.GEOFENCE_EXIT, ActivityLogType.PAIRING_ACCEPTED + }; + + for (ActivityLogType type : types) { + activityLogService.createLog(sampleUser, type, "Deskripsi", null); + } + + verify(activityLogRepository, times(types.length)).save(any(ActivityLog.class)); + } + + // ===== GET LOGS TESTS ===== + + @Test + @DisplayName("getLogs - harus return halaman log berdasarkan userId") + void getLogs_shouldReturnPagedLogs() { + sampleLog.setId(10L); + Page page = new PageImpl<>(List.of(sampleLog)); + Pageable pageable = PageRequest.of(0, 10); + + when(activityLogRepository.findByUser_IdOrderByCreatedAtDesc(1L, pageable)) + .thenReturn(page); + + Page result = activityLogService.getLogs(1L, pageable); + + assertThat(result.getTotalElements()).isEqualTo(1); + assertThat(result.getContent().get(0).getId()).isEqualTo(10L); + assertThat(result.getContent().get(0).getLogType()).isEqualTo("LOGIN"); + assertThat(result.getContent().get(0).getDescription()).isEqualTo("User login"); + } + + @Test + @DisplayName("getLogs - tidak ada log harus return halaman kosong") + void getLogs_noLogs_shouldReturnEmptyPage() { + Pageable pageable = PageRequest.of(0, 10); + when(activityLogRepository.findByUser_IdOrderByCreatedAtDesc(1L, pageable)) + .thenReturn(Page.empty()); + + Page result = activityLogService.getLogs(1L, pageable); + + assertThat(result.getTotalElements()).isZero(); + assertThat(result.getContent()).isEmpty(); + } + + // ===== GET LOGS FOR GUARDIAN TESTS ===== + + @Test + @DisplayName("getLogsForGuardian - Guardian dengan pairing aktif harus return log user") + void getLogsForGuardian_activePairing_shouldReturnUserLogs() { + PairingRelation pairing = PairingRelation.builder() + .id(5L).guardian(sampleGuardian).user(sampleUser) + .status(PairingStatus.ACTIVE).build(); + + sampleLog.setId(10L); + Page page = new PageImpl<>(List.of(sampleLog)); + Pageable pageable = PageRequest.of(0, 5); + + when(pairingRelationRepository.findByGuardian_IdAndStatus(2L, PairingStatus.ACTIVE)) + .thenReturn(Optional.of(pairing)); + when(activityLogRepository.findByUser_IdOrderByCreatedAtDesc(1L, pageable)) + .thenReturn(page); + + Page result = activityLogService.getLogsForGuardian(2L, pageable); + + assertThat(result.getTotalElements()).isEqualTo(1); + assertThat(result.getContent().get(0).getLogType()).isEqualTo("LOGIN"); + } + + @Test + @DisplayName("getLogsForGuardian - Guardian tanpa pairing aktif harus throw ResourceNotFoundException") + void getLogsForGuardian_noPairing_shouldThrow() { + when(pairingRelationRepository.findByGuardian_IdAndStatus(2L, PairingStatus.ACTIVE)) + .thenReturn(Optional.empty()); + + assertThatThrownBy(() -> activityLogService.getLogsForGuardian(2L, PageRequest.of(0, 10))) + .isInstanceOf(ResourceNotFoundException.class) + .hasMessageContaining("Belum ada user yang dipair"); + + verify(activityLogRepository, never()).findByUser_IdOrderByCreatedAtDesc(any(), any()); + } + + @Test + @DisplayName("getLogsForGuardian - paginasi multi-halaman harus diteruskan ke repository") + void getLogsForGuardian_pagination_shouldPassPageableToRepository() { + PairingRelation pairing = PairingRelation.builder() + .id(5L).guardian(sampleGuardian).user(sampleUser) + .status(PairingStatus.ACTIVE).build(); + + Pageable page2 = PageRequest.of(1, 10); + when(pairingRelationRepository.findByGuardian_IdAndStatus(2L, PairingStatus.ACTIVE)) + .thenReturn(Optional.of(pairing)); + when(activityLogRepository.findByUser_IdOrderByCreatedAtDesc(1L, page2)) + .thenReturn(Page.empty()); + + activityLogService.getLogsForGuardian(2L, page2); + + verify(activityLogRepository).findByUser_IdOrderByCreatedAtDesc(1L, page2); + } +} \ No newline at end of file