diff --git a/pom.xml b/pom.xml index 0d25261..2d2838b 100644 --- a/pom.xml +++ b/pom.xml @@ -118,6 +118,12 @@ test + + org.springframework.security + spring-security-test + test + + com.github.librepdf openpdf @@ -160,6 +166,14 @@ + + + org.apache.maven.plugins + maven-surefire-plugin + + -javaagent:${settings.localRepository}/net/bytebuddy/byte-buddy-agent/1.17.8/byte-buddy-agent-1.17.8.jar + + org.codehaus.mojo build-helper-maven-plugin diff --git a/src/main/java/com/crimeLink/analyzer/config/SecurityConfig.java b/src/main/java/com/crimeLink/analyzer/config/SecurityConfig.java index 2f03c43..acfb43c 100644 --- a/src/main/java/com/crimeLink/analyzer/config/SecurityConfig.java +++ b/src/main/java/com/crimeLink/analyzer/config/SecurityConfig.java @@ -107,7 +107,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); - } + } @Bean public AuthenticationProvider authenticationProvider() { diff --git a/src/main/java/com/crimeLink/analyzer/controller/AuthController.java b/src/main/java/com/crimeLink/analyzer/controller/AuthController.java index 4741a85..2361fd4 100644 --- a/src/main/java/com/crimeLink/analyzer/controller/AuthController.java +++ b/src/main/java/com/crimeLink/analyzer/controller/AuthController.java @@ -84,7 +84,7 @@ public ResponseEntity logout(@AuthenticationPrincipal User user) { @GetMapping("/me") public ResponseEntity getCurrentUser(@AuthenticationPrincipal User user) { - if (user == null) { + if (user == null || user.getUserId() == null) { return ResponseEntity.status(401).body(Map.of("message", "Not authenticated")); } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 0a0605d..15607eb 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,3 +1,5 @@ +spring.config.import=optional:file:.env.properties + spring.application.name=CrimeLinkAnalyzer # Server Configuration diff --git a/src/test/java/com/crimeLink/analyzer/controller/AdminControllerTest.java b/src/test/java/com/crimeLink/analyzer/controller/AdminControllerTest.java new file mode 100644 index 0000000..3cf944b --- /dev/null +++ b/src/test/java/com/crimeLink/analyzer/controller/AdminControllerTest.java @@ -0,0 +1,239 @@ +package com.crimeLink.analyzer.controller; + +import com.crimeLink.analyzer.dto.AuditLogDTO; +import com.crimeLink.analyzer.entity.BackupMetadata; +import com.crimeLink.analyzer.entity.LoginAudit; +import com.crimeLink.analyzer.entity.User; +import com.crimeLink.analyzer.repository.LoginAuditRepository; +import com.crimeLink.analyzer.repository.UserRepository; +import com.crimeLink.analyzer.service.BackupService; +import com.crimeLink.analyzer.service.SystemSettingsService; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.*; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.security.crypto.password.PasswordEncoder; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class AdminControllerTest { + + @Mock private UserRepository userRepo; + @Mock private LoginAuditRepository auditRepo; + @Mock private PasswordEncoder passwordEncoder; + @Mock private BackupService backupService; + @Mock private SystemSettingsService settingsService; + @Mock private Authentication authentication; + + @InjectMocks + private AdminController controller; + + @Test + void getUsers_shouldReturnAll_whenNoFilters() { + when(userRepo.findAll()).thenReturn(List.of(new User(), new User())); + + ResponseEntity> response = controller.getUsers(null, null); + + assertEquals(200, response.getStatusCode().value()); + assertEquals(2, response.getBody().size()); + } + + @Test + void getUsers_shouldReturnByRoleAndStatus() { + when(userRepo.findByRoleAndStatus("Admin", "Active")).thenReturn(List.of(new User())); + + ResponseEntity> response = controller.getUsers("Admin", "Active"); + + assertEquals(200, response.getStatusCode().value()); + assertEquals(1, response.getBody().size()); + } + + @Test + void createUser_shouldReturn400_whenEmailExists() { + User user = new User(); + user.setEmail("a@test.com"); + + when(userRepo.existsByEmail("a@test.com")).thenReturn(true); + + ResponseEntity response = controller.createUser(user); + + assertEquals(400, response.getStatusCode().value()); + } + + @Test + void createUser_shouldEncodePasswordAndSave() { + User user = new User(); + user.setEmail("a@test.com"); + user.setPasswordHash("plain"); + + when(userRepo.existsByEmail("a@test.com")).thenReturn(false); + when(passwordEncoder.encode("plain")).thenReturn("hashed"); + when(userRepo.save(any(User.class))).thenAnswer(inv -> inv.getArgument(0)); + + ResponseEntity response = controller.createUser(user); + + assertEquals(200, response.getStatusCode().value()); + User saved = (User) response.getBody(); + assertEquals("hashed", saved.getPasswordHash()); + } + + @Test + void updateUser_shouldReturn404_whenMissing() { + when(userRepo.findById(1)).thenReturn(Optional.empty()); + + ResponseEntity response = controller.updateUser(1, new User()); + + assertEquals(404, response.getStatusCode().value()); + } + + @Test + void updateUser_shouldUpdateAndEncodePassword() { + User existing = new User(); + existing.setUserId(1); + + User updated = new User(); + updated.setName("New"); + updated.setEmail("new@test.com"); + updated.setPasswordHash("pw"); + + when(userRepo.findById(1)).thenReturn(Optional.of(existing)); + when(passwordEncoder.encode("pw")).thenReturn("hashed"); + when(userRepo.save(any(User.class))).thenAnswer(inv -> inv.getArgument(0)); + + ResponseEntity response = controller.updateUser(1, updated); + + assertEquals(200, response.getStatusCode().value()); + User body = (User) response.getBody(); + assertEquals("New", body.getName()); + assertEquals("hashed", body.getPasswordHash()); + } + + @Test + void deactivateUser_shouldReturn404_whenMissing() { + when(userRepo.findById(1)).thenReturn(Optional.empty()); + + ResponseEntity response = controller.deactivateUser(1); + + assertEquals(404, response.getStatusCode().value()); + } + + @Test + void deactivateUser_shouldSetInactive() { + User user = new User(); + user.setUserId(1); + user.setStatus("Active"); + + when(userRepo.findById(1)).thenReturn(Optional.of(user)); + + ResponseEntity response = controller.deactivateUser(1); + + assertEquals(200, response.getStatusCode().value()); + assertEquals("Inactive", user.getStatus()); + verify(userRepo).save(user); + } + + @Test + void getAuditLogs_shouldMapDtos() { + LoginAudit log = new LoginAudit(); + log.setAuditId(10L); + log.setUserId(1); + log.setEmail("u@test.com"); + log.setIpAddress("127.0.0.1"); + log.setLoginTime(LocalDateTime.now()); + log.setSuccess(false); + log.setFailureReason("Invalid password"); + + User user = new User(); + user.setUserId(1); + user.setName("Officer A"); + + Page page = new PageImpl<>(List.of(log)); + when(auditRepo.findAll(any(PageRequest.class))).thenReturn(page); + when(userRepo.findById(1)).thenReturn(Optional.of(user)); + + ResponseEntity> response = controller.getAuditLogs(100, 0); + + assertEquals(200, response.getStatusCode().value()); + assertEquals(1, response.getBody().size()); + assertEquals("Officer A", response.getBody().get(0).getUserName()); + } + + @Test + void triggerBackup_shouldReturn200() { + BackupMetadata metadata = new BackupMetadata(); + metadata.setFilename("backup.zip"); + metadata.setSizeBytes(100L); + metadata.setCreatedAt(LocalDateTime.now()); + + when(authentication.getName()).thenReturn("admin@test.com"); + when(backupService.createBackup("admin@test.com")).thenReturn(metadata); + + ResponseEntity response = controller.triggerBackup(authentication); + + assertEquals(200, response.getStatusCode().value()); + } + + @Test + void restoreBackup_shouldReturn400_whenFilenameMissing() { + ResponseEntity response = controller.restoreBackup(Map.of(), authentication); + + assertEquals(400, response.getStatusCode().value()); + } + + @Test + void restoreBackup_shouldReturn200_whenValid() { + when(authentication.getName()).thenReturn("admin@test.com"); + + ResponseEntity response = controller.restoreBackup( + Map.of("filename", "backup.zip"), authentication); + + assertEquals(200, response.getStatusCode().value()); + verify(backupService).restoreBackup("backup.zip", "admin@test.com"); + } + + @Test + void listBackups_shouldReturnList() { + when(backupService.listBackups()).thenReturn(List.of(new BackupMetadata())); + + ResponseEntity> response = controller.listBackups(); + + assertEquals(200, response.getStatusCode().value()); + assertEquals(1, response.getBody().size()); + } + + @Test + void getSettings_shouldReturnMap() { + when(settingsService.getAllSettings()).thenReturn(Map.of("theme", "dark")); + + ResponseEntity> response = controller.getSettings(); + + assertEquals(200, response.getStatusCode().value()); + assertEquals("dark", response.getBody().get("theme")); + } + + @Test + void updateSettings_shouldReturn200() { + when(settingsService.updateSettings(anyMap())).thenReturn(Map.of("theme", "dark")); + + ResponseEntity response = controller.updateSettings(Map.of("theme", "dark")); + + assertEquals(200, response.getStatusCode().value()); + } + + @Test + void getSystemHealth_shouldReturnUp() { + ResponseEntity response = controller.getSystemHealth(); + assertEquals(200, response.getStatusCode().value()); + } +} \ No newline at end of file diff --git a/src/test/java/com/crimeLink/analyzer/controller/AuthControllerTest.java b/src/test/java/com/crimeLink/analyzer/controller/AuthControllerTest.java new file mode 100644 index 0000000..d82c4de --- /dev/null +++ b/src/test/java/com/crimeLink/analyzer/controller/AuthControllerTest.java @@ -0,0 +1,136 @@ +package com.crimeLink.analyzer.controller; + +import com.crimeLink.analyzer.dto.LoginRequest; +import com.crimeLink.analyzer.dto.LoginResponse; +import com.crimeLink.analyzer.dto.TokenRefreshRequest; +import com.crimeLink.analyzer.entity.RefreshToken; +import com.crimeLink.analyzer.entity.User; +import com.crimeLink.analyzer.service.AuthService; +import com.crimeLink.analyzer.service.JwtService; +import com.crimeLink.analyzer.service.RefreshTokenService; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.util.Optional; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@ExtendWith(MockitoExtension.class) +class AuthControllerTest { + + @InjectMocks + private AuthController controller; + + @Mock private AuthService authService; + @Mock private RefreshTokenService refreshTokenService; + @Mock private JwtService jwtService; + + private MockMvc mockMvc; + private final ObjectMapper objectMapper = new ObjectMapper(); + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(controller).build(); + } + + @Test + void login_shouldReturn200_whenSuccess() throws Exception { + LoginResponse response = new LoginResponse(true, "Login successful", "access", "refresh", null); + when(authService.login(any(LoginRequest.class), any(HttpServletRequest.class))).thenReturn(response); + + LoginRequest request = new LoginRequest(); + request.setEmail("test@test.com"); + request.setPassword("123456"); + + mockMvc.perform(post("/api/auth/login") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)); + } + + @Test + void login_shouldReturn401_whenFailure() throws Exception { + LoginResponse response = new LoginResponse(false, "Invalid email or password", null, null, null); + when(authService.login(any(LoginRequest.class), any(HttpServletRequest.class))).thenReturn(response); + + LoginRequest request = new LoginRequest(); + request.setEmail("test@test.com"); + request.setPassword("wrong"); + + mockMvc.perform(post("/api/auth/login") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isUnauthorized()); + } + + @Test + void refreshToken_shouldReturn400_whenMissing() throws Exception { + TokenRefreshRequest request = new TokenRefreshRequest(); + request.setRefreshToken(""); + + mockMvc.perform(post("/api/auth/refresh") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()); + } + + @Test + void refreshToken_shouldReturn200_whenValid() throws Exception { + User user = new User(); + user.setUserId(1); + + RefreshToken oldToken = new RefreshToken(); + oldToken.setToken("old"); + oldToken.setUser(user); + + RefreshToken newToken = new RefreshToken(); + newToken.setToken("new"); + newToken.setUser(user); + + when(refreshTokenService.findValidToken("old")).thenReturn(Optional.of(oldToken)); + when(refreshTokenService.rotateRefreshToken(oldToken)).thenReturn(newToken); + when(jwtService.generateToken(user)).thenReturn("access-new"); + + TokenRefreshRequest request = new TokenRefreshRequest(); + request.setRefreshToken("old"); + + mockMvc.perform(post("/api/auth/refresh") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.accessToken").value("access-new")); + } + + @Test + void logout_shouldReturn200() throws Exception { + User principal = new User(); + principal.setEmail("user@test.com"); + principal.setPasswordHash("pw"); + principal.setRole("Admin"); + principal.setStatus("Active"); + + mockMvc.perform(post("/api/auth/logout").with(user(principal))) + .andExpect(status().isOk()); + } + + @Test + void getCurrentUser_shouldReturn401_whenUnauthenticated() throws Exception { + mockMvc.perform(get("/api/auth/me")) + .andExpect(status().isUnauthorized()); + } +} \ No newline at end of file diff --git a/src/test/java/com/crimeLink/analyzer/controller/CallAnalysisControllerTest.java b/src/test/java/com/crimeLink/analyzer/controller/CallAnalysisControllerTest.java new file mode 100644 index 0000000..8979da6 --- /dev/null +++ b/src/test/java/com/crimeLink/analyzer/controller/CallAnalysisControllerTest.java @@ -0,0 +1,106 @@ +package com.crimeLink.analyzer.controller; + +import com.crimeLink.analyzer.service.CallAnalysisService; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@ExtendWith(MockitoExtension.class) +class CallAnalysisControllerTest { + + @Mock + private CallAnalysisService callAnalysisService; + + @InjectMocks + private CallAnalysisController controller; + + private MockMvc mockMvc; + private final ObjectMapper mapper = new ObjectMapper(); + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(controller).build(); + } + + @Test + void analyzeCallRecord_shouldReturn400_whenNoFile() throws Exception { + MockMultipartFile file = new MockMultipartFile("file", "", "application/pdf", new byte[0]); + + mockMvc.perform(multipart("/api/call-analysis/analyze").file(file)) + .andExpect(status().isBadRequest()); + } + + @Test + void analyzeCallRecord_shouldReturn400_whenWrongType() throws Exception { + MockMultipartFile file = new MockMultipartFile("file", "a.txt", "text/plain", "abc".getBytes()); + + mockMvc.perform(multipart("/api/call-analysis/analyze").file(file)) + .andExpect(status().isBadRequest()); + } + + @Test + void analyzeCallRecord_shouldReturn200_whenValid() throws Exception { + MockMultipartFile file = new MockMultipartFile("file", "a.pdf", "application/pdf", "abc".getBytes()); + ObjectNode result = mapper.createObjectNode().put("status", "done"); + + when(callAnalysisService.analyzeCallRecord(any())).thenReturn(result); + + mockMvc.perform(multipart("/api/call-analysis/analyze").file(file)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.status").value("done")); + } + + @Test + void analyzeBatch_shouldReturn400_whenEmpty() throws Exception { + mockMvc.perform(multipart("/api/call-analysis/analyze/batch")) + .andExpect(status().isBadRequest()); + } + + @Test + void analyzeBatch_shouldReturn200_whenValid() throws Exception { + MockMultipartFile f1 = new MockMultipartFile("files", "a.pdf", "application/pdf", "abc".getBytes()); + MockMultipartFile f2 = new MockMultipartFile("files", "b.pdf", "application/pdf", "def".getBytes()); + ObjectNode result = mapper.createObjectNode().put("batch", "ok"); + + when(callAnalysisService.analyzeBatch(anyList())).thenReturn(result); + + mockMvc.perform(multipart("/api/call-analysis/analyze/batch").file(f1).file(f2)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.batch").value("ok")); + } + + @Test + void health_shouldReturn200_whenHealthy() throws Exception { + ObjectNode node = mapper.createObjectNode().put("status", "healthy"); + when(callAnalysisService.checkHealth()).thenReturn(node); + + mockMvc.perform(get("/api/call-analysis/health")) + .andExpect(status().isOk()); + } + + @Test + void health_shouldReturn503_whenUnhealthy() throws Exception { + ObjectNode node = mapper.createObjectNode().put("status", "down"); + when(callAnalysisService.checkHealth()).thenReturn(node); + + mockMvc.perform(get("/api/call-analysis/health")) + .andExpect(status().isServiceUnavailable()); + } +} \ No newline at end of file diff --git a/src/test/java/com/crimeLink/analyzer/controller/DutyRecommendationControllerTest.java b/src/test/java/com/crimeLink/analyzer/controller/DutyRecommendationControllerTest.java new file mode 100644 index 0000000..36f3abd --- /dev/null +++ b/src/test/java/com/crimeLink/analyzer/controller/DutyRecommendationControllerTest.java @@ -0,0 +1,60 @@ +package com.crimeLink.analyzer.controller; + +import com.crimeLink.analyzer.dto.DutyRecommendationRequest; +import com.crimeLink.analyzer.dto.OfficerRecommendationDTO; +import com.crimeLink.analyzer.service.DutyRecommendationService; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.*; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.time.LocalDate; +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@ExtendWith(org.mockito.junit.jupiter.MockitoExtension.class) +class DutyRecommendationControllerTest { + + @InjectMocks + private DutyRecommendationController controller; + + @Mock + private DutyRecommendationService recommendationService; + + private MockMvc mockMvc; + private final ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules(); + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(controller).build(); + } + + @Test + void getRecommendations_shouldReturn200() throws Exception { + OfficerRecommendationDTO dto = new OfficerRecommendationDTO( + 1, "Officer A", null, 90.0, "Unknown", + LocalDate.now(), 2, false, "Good fit" + ); + + when(recommendationService.getOfficerRecommendations(any(DutyRecommendationRequest.class))) + .thenReturn(List.of(dto)); + + DutyRecommendationRequest request = new DutyRecommendationRequest(); + request.setDate(LocalDate.now()); + + mockMvc.perform(post("/api/duty-recommendations") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].officerId").value(1)) + .andExpect(jsonPath("$[0].name").value("Officer A")); + } +} \ No newline at end of file diff --git a/src/test/java/com/crimeLink/analyzer/controller/DutyScheduleControllerTest.java b/src/test/java/com/crimeLink/analyzer/controller/DutyScheduleControllerTest.java new file mode 100644 index 0000000..f4a2707 --- /dev/null +++ b/src/test/java/com/crimeLink/analyzer/controller/DutyScheduleControllerTest.java @@ -0,0 +1,132 @@ +package com.crimeLink.analyzer.controller; + +import com.crimeLink.analyzer.dto.DutyScheduleRequest; +import com.crimeLink.analyzer.dto.OfficerDutyRowDTO; +import com.crimeLink.analyzer.entity.DutySchedule; +import com.crimeLink.analyzer.enums.DutyStatus; +import com.crimeLink.analyzer.service.DutyScheduleService; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.*; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.time.LocalDate; +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@ExtendWith(org.mockito.junit.jupiter.MockitoExtension.class) +class DutyScheduleControllerTest { + + @InjectMocks + private DutyScheduleController controller; + + @Mock + private DutyScheduleService dutyService; + + private MockMvc mockMvc; + private final ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules(); + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(controller).build(); + } + + @Test + void getOfficersForDate_shouldReturn200() throws Exception { + OfficerDutyRowDTO dto = new OfficerDutyRowDTO(1, "Officer A", "Matara", "08:00-12:00", "ACTIVE", "Desc"); + + when(dutyService.getOfficerRowsForDate(LocalDate.of(2026, 3, 13))).thenReturn(List.of(dto)); + + mockMvc.perform(get("/api/duty-schedules/officers") + .param("date", "2026-03-13")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].officerId").value(1)); + } + + @Test + void getDutyLocations_shouldReturn200() throws Exception { + when(dutyService.getDutyLocations()).thenReturn(List.of("Matara", "Hakmana")); + + mockMvc.perform(get("/api/duty-schedules/locations")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0]").value("Matara")); + } + + @Test + void createDuty_shouldReturn201_whenCreated() throws Exception { + DutySchedule duty = new DutySchedule(); + duty.setId(1L); + + when(dutyService.createDuty(any(DutyScheduleRequest.class))).thenReturn(duty); + + DutyScheduleRequest request = new DutyScheduleRequest(); + request.setOfficerId(1); + request.setDate(LocalDate.of(2026, 3, 13)); + request.setStatus(DutyStatus.Active); + request.setTimeRange("08:00-12:00"); + + mockMvc.perform(post("/api/duty-schedules") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isCreated()); + } + + @Test + void createDuty_shouldReturn400_whenValidationError() throws Exception { + when(dutyService.createDuty(any(DutyScheduleRequest.class))) + .thenThrow(new IllegalArgumentException("Status is required")); + + DutyScheduleRequest request = new DutyScheduleRequest(); + + mockMvc.perform(post("/api/duty-schedules") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()) + .andExpect(content().string("Status is required")); + } + + @Test + void createDuty_shouldReturn500_whenUnexpectedError() throws Exception { + when(dutyService.createDuty(any(DutyScheduleRequest.class))) + .thenThrow(new RuntimeException("DB error")); + + DutyScheduleRequest request = new DutyScheduleRequest(); + + mockMvc.perform(post("/api/duty-schedules") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isInternalServerError()) + .andExpect(content().string("Failed to save duty")); + } + + @Test + void getDutiesInRange_shouldReturn200() throws Exception { + when(dutyService.getDutiesBetween(LocalDate.of(2026, 3, 1), LocalDate.of(2026, 3, 31))) + .thenReturn(List.of(new DutySchedule())); + + mockMvc.perform(get("/api/duty-schedules/range") + .param("start", "2026-03-01") + .param("end", "2026-03-31")) + .andExpect(status().isOk()); + } + + @Test + void getDutyScheduleReportPdf_shouldReturnPdf() throws Exception { + when(dutyService.generateDutyScheduleReportPdf(LocalDate.of(2026, 3, 1), LocalDate.of(2026, 3, 31))) + .thenReturn("pdf-content".getBytes()); + + mockMvc.perform(get("/api/duty-schedules/report/pdf") + .param("start", "2026-03-01") + .param("end", "2026-03-31")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_PDF)); + } +} \ No newline at end of file diff --git a/src/test/java/com/crimeLink/analyzer/controller/FacialRecognitionControllerTest.java b/src/test/java/com/crimeLink/analyzer/controller/FacialRecognitionControllerTest.java new file mode 100644 index 0000000..7e96afa --- /dev/null +++ b/src/test/java/com/crimeLink/analyzer/controller/FacialRecognitionControllerTest.java @@ -0,0 +1,163 @@ +package com.crimeLink.analyzer.controller; + +import com.crimeLink.analyzer.service.FacialRecognitionService; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.security.authentication.TestingAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@ExtendWith(MockitoExtension.class) +class FacialRecognitionControllerTest { + + @Mock + private FacialRecognitionService facialRecognitionService; + + @InjectMocks + private FacialRecognitionController controller; + + private MockMvc mockMvc; + private final ObjectMapper mapper = new ObjectMapper(); + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(controller).build(); + SecurityContextHolder.getContext().setAuthentication( + new TestingAuthenticationToken("user-1", null) + ); + } + + @AfterEach + void tearDown() { + SecurityContextHolder.clearContext(); + } + + @Test + void analyzeImage_shouldReturn400_whenNoImage() throws Exception { + MockMultipartFile image = new MockMultipartFile("image", "", "image/jpeg", new byte[0]); + + mockMvc.perform(multipart("/api/facial/analyze").file(image)) + .andExpect(status().isBadRequest()); + } + + @Test + void analyzeImage_shouldReturn400_whenWrongType() throws Exception { + MockMultipartFile image = new MockMultipartFile("image", "a.txt", "text/plain", "abc".getBytes()); + + mockMvc.perform(multipart("/api/facial/analyze").file(image)) + .andExpect(status().isBadRequest()); + } + + @Test + void analyzeImage_shouldReturn400_whenThresholdInvalid() throws Exception { + MockMultipartFile image = new MockMultipartFile("image", "a.jpg", "image/jpeg", "abc".getBytes()); + + mockMvc.perform(multipart("/api/facial/analyze") + .file(image) + .param("threshold", "101")) + .andExpect(status().isBadRequest()); + } + + @Test + void analyzeImage_shouldReturn200_whenValid() throws Exception { + MockMultipartFile image = new MockMultipartFile("image", "a.jpg", "image/jpeg", "abc".getBytes()); + ObjectNode result = mapper.createObjectNode().put("match", "ok"); + + when(facialRecognitionService.analyzeImage(any(), eq(45.0f), eq("unknown"), isNull())) + .thenReturn(result); + + mockMvc.perform(multipart("/api/facial/analyze").file(image)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.match").value("ok")); + } + + @Test + void registerCriminal_shouldReturn400_whenNameMissing() throws Exception { + MockMultipartFile photo = new MockMultipartFile("photo", "a.jpg", "image/jpeg", "abc".getBytes()); + + mockMvc.perform(multipart("/api/facial/register") + .file(photo) + .param("name", "") + .param("nic", "123")) + .andExpect(status().isBadRequest()); + } + + @Test + void registerCriminal_shouldReturn400_whenNicMissing() throws Exception { + MockMultipartFile photo = new MockMultipartFile("photo", "a.jpg", "image/jpeg", "abc".getBytes()); + + mockMvc.perform(multipart("/api/facial/register") + .file(photo) + .param("name", "John") + .param("nic", "")) + .andExpect(status().isBadRequest()); + } + + @Test + void registerCriminal_shouldReturn200_whenValid() throws Exception { + MockMultipartFile photo = new MockMultipartFile("photo", "a.jpg", "image/jpeg", "abc".getBytes()); + ObjectNode result = mapper.createObjectNode().put("registered", "yes"); + + when(facialRecognitionService.registerCriminal( + any(), any(), eq("John"), eq("123"), any(), any(), any(), any(), any(), any(), any(), any(), any() + )).thenReturn(result); + + mockMvc.perform(multipart("/api/facial/register") + .file(photo) + .param("name", "John") + .param("nic", "123")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.registered").value("yes")); + } + + @Test + void getCriminals_shouldReturn200() throws Exception { + ObjectNode result = mapper.createObjectNode().put("count", 1); + when(facialRecognitionService.getCriminals()).thenReturn(result); + + mockMvc.perform(get("/api/facial/criminals")) + .andExpect(status().isOk()); + } + + @Test + void getRecognitionHistory_shouldReturn200() throws Exception { + ObjectNode result = mapper.createObjectNode().put("history", "ok"); + when(facialRecognitionService.getRecognitionHistory(50)).thenReturn(result); + + mockMvc.perform(get("/api/facial/history")) + .andExpect(status().isOk()); + } + + @Test + void health_shouldReturn200_whenHealthy() throws Exception { + ObjectNode result = mapper.createObjectNode().put("status", "healthy"); + when(facialRecognitionService.checkHealth()).thenReturn(result); + + mockMvc.perform(get("/api/facial/health")) + .andExpect(status().isOk()); + } + + @Test + void health_shouldReturn503_whenUnhealthy() throws Exception { + ObjectNode result = mapper.createObjectNode().put("status", "down"); + when(facialRecognitionService.checkHealth()).thenReturn(result); + + mockMvc.perform(get("/api/facial/health")) + .andExpect(status().isServiceUnavailable()); + } +} \ No newline at end of file diff --git a/src/test/java/com/crimeLink/analyzer/controller/LeaveControllerTest.java b/src/test/java/com/crimeLink/analyzer/controller/LeaveControllerTest.java new file mode 100644 index 0000000..90fdd8a --- /dev/null +++ b/src/test/java/com/crimeLink/analyzer/controller/LeaveControllerTest.java @@ -0,0 +1,100 @@ +package com.crimeLink.analyzer.controller; + +import com.crimeLink.analyzer.dto.LeaveRequestDTO; +import com.crimeLink.analyzer.dto.LeaveSubmitRequest; +import com.crimeLink.analyzer.dto.LeaveUpdateRequest; +import com.crimeLink.analyzer.service.LeaveService; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.*; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.time.LocalDate; +import java.util.List; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@ExtendWith(org.mockito.junit.jupiter.MockitoExtension.class) +class LeaveControllerTest { + + @InjectMocks + private LeaveController controller; + + @Mock + private LeaveService leaveService; + + private MockMvc mockMvc; + private final ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules(); + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(controller).build(); + } + + @Test + void submitLeaveRequest_shouldReturn200() throws Exception { + LeaveRequestDTO dto = new LeaveRequestDTO(); + dto.setId(1L); + dto.setStatus("PENDING"); + + when(leaveService.submitLeaveRequest(any(LeaveSubmitRequest.class))).thenReturn(dto); + + LeaveSubmitRequest request = new LeaveSubmitRequest(); + request.setOfficerId(1L); + request.setDate(LocalDate.of(2026, 3, 13)); + request.setReason("Medical"); + + mockMvc.perform(post("/api/leaves/request") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.status").value("PENDING")); + } + + @Test + void getOfficerLeaves_shouldReturn200() throws Exception { + LeaveRequestDTO dto = new LeaveRequestDTO(); + dto.setId(1L); + + when(leaveService.getOfficerLeaves(1L)).thenReturn(List.of(dto)); + + mockMvc.perform(get("/api/leaves/officer/1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].id").value(1)); + } + + @Test + void getAllLeaveRequests_shouldReturn200() throws Exception { + when(leaveService.getAllLeaveRequests("2026-03")).thenReturn(List.of(new LeaveRequestDTO())); + + mockMvc.perform(get("/api/leaves/all").param("month", "2026-03")) + .andExpect(status().isOk()); + } + + @Test + void updateLeaveStatus_shouldUseHeaderUserId_whenProvided() throws Exception { + LeaveRequestDTO dto = new LeaveRequestDTO(); + dto.setId(1L); + dto.setStatus("APPROVED"); + + when(leaveService.updateLeaveStatus(eq(1L), any(LeaveUpdateRequest.class), eq(5L))).thenReturn(dto); + + LeaveUpdateRequest request = new LeaveUpdateRequest(); + request.setStatus("APPROVED"); + request.setResponseReason("Approved"); + + mockMvc.perform(put("/api/leaves/1/status") + .header("X-User-Id", "5") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.status").value("APPROVED")); + } +} \ No newline at end of file diff --git a/src/test/java/com/crimeLink/analyzer/controller/MobileDutyControllerTest.java b/src/test/java/com/crimeLink/analyzer/controller/MobileDutyControllerTest.java new file mode 100644 index 0000000..15ba82c --- /dev/null +++ b/src/test/java/com/crimeLink/analyzer/controller/MobileDutyControllerTest.java @@ -0,0 +1,79 @@ +package com.crimeLink.analyzer.controller; + +import com.crimeLink.analyzer.dto.DutyAssignmentDTO; +import com.crimeLink.analyzer.dto.DutyDetailDTO; +import com.crimeLink.analyzer.service.MobileDutyService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.*; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.util.List; + +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@ExtendWith(org.mockito.junit.jupiter.MockitoExtension.class) +class MobileDutyControllerTest { + + @InjectMocks + private MobileDutyController controller; + + @Mock + private MobileDutyService mobileDutyService; + + private MockMvc mockMvc; + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(controller).build(); + } + + @Test + void getDutiesByOfficer_shouldReturn200() throws Exception { + DutyAssignmentDTO dto = new DutyAssignmentDTO( + 1L, "1", "Officer A", "2026-03-13", + "Matara", "08:00-12:00", "ACTIVE", "General", "Desc", 0 + ); + + when(mobileDutyService.getDutiesByOfficerId(1L)).thenReturn(List.of(dto)); + + mockMvc.perform(get("/api/duties/officer/1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].officerName").value("Officer A")); + } + + @Test + void getDutiesByOfficer_shouldReturn500_whenServiceFails() throws Exception { + when(mobileDutyService.getDutiesByOfficerId(1L)).thenThrow(new RuntimeException("error")); + + mockMvc.perform(get("/api/duties/officer/1")) + .andExpect(status().isInternalServerError()); + } + + @Test + void getDutyDetailsByDate_shouldReturn200() throws Exception { + DutyDetailDTO dto = new DutyDetailDTO( + 1L, "General", "Matara", "08:00-12:00", "ACTIVE", "N/A", 0, "Desc" + ); + + when(mobileDutyService.getDutyDetailsByDate(1L, java.time.LocalDate.of(2026, 3, 13))) + .thenReturn(List.of(dto)); + + mockMvc.perform(get("/api/duties/officer/1/date").param("date", "2026-03-13")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].location").value("Matara")); + } + + @Test + void getDutyDetailsByDate_shouldReturn500_whenServiceFails() throws Exception { + when(mobileDutyService.getDutyDetailsByDate(1L, java.time.LocalDate.of(2026, 3, 13))) + .thenThrow(new RuntimeException("error")); + + mockMvc.perform(get("/api/duties/officer/1/date").param("date", "2026-03-13")) + .andExpect(status().isInternalServerError()); + } +} \ No newline at end of file diff --git a/src/test/java/com/crimeLink/analyzer/controller/SafetyLocationControllerTest.java b/src/test/java/com/crimeLink/analyzer/controller/SafetyLocationControllerTest.java new file mode 100644 index 0000000..22928c3 --- /dev/null +++ b/src/test/java/com/crimeLink/analyzer/controller/SafetyLocationControllerTest.java @@ -0,0 +1,37 @@ +package com.crimeLink.analyzer.controller; + +import com.crimeLink.analyzer.entity.SafetyLocation; +import com.crimeLink.analyzer.entity.SafetyType; +import com.crimeLink.analyzer.service.SafetyLocationService; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class SafetyLocationControllerTest { + + @InjectMocks + private SafetyLocationController controller; + + @Mock + private SafetyLocationService service; + + @Test + void getByType_shouldReturnAll_whenTypeMissing() { + when(service.getAllLocations()).thenReturn(List.of(new SafetyLocation())); + assertEquals(1, controller.getByType(null).size()); + } + + @Test + void getByType_shouldReturnFiltered_whenTypeProvided() { + when(service.getByType(SafetyType.POLICE)).thenReturn(List.of(new SafetyLocation())); + assertEquals(1, controller.getByType("police").size()); + } +} \ No newline at end of file diff --git a/src/test/java/com/crimeLink/analyzer/controller/UserControllerTest.java b/src/test/java/com/crimeLink/analyzer/controller/UserControllerTest.java new file mode 100644 index 0000000..7ed4d50 --- /dev/null +++ b/src/test/java/com/crimeLink/analyzer/controller/UserControllerTest.java @@ -0,0 +1,41 @@ +package com.crimeLink.analyzer.controller; + +import com.crimeLink.analyzer.entity.User; +import com.crimeLink.analyzer.service.UserService; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +@ExtendWith(org.mockito.junit.jupiter.MockitoExtension.class) +class UserControllerTest { + + @InjectMocks + private UserController controller; + + @Mock + private UserService service; + + @Test + void getAllOfficers_shouldReturnList() { + when(service.getAllOfficers()).thenReturn(List.of(new User(), new User())); + + List result = controller.getAllOfficers(); + + assertEquals(2, result.size()); + } + + @Test + void getFieldOfficers_shouldReturnList() { + when(service.getFieldOfficers()).thenReturn(List.of(new User())); + + List result = controller.getFieldOfficers(); + + assertEquals(1, result.size()); + } +} \ No newline at end of file diff --git a/src/test/java/com/crimeLink/analyzer/controller/VehicleControllerTest.java b/src/test/java/com/crimeLink/analyzer/controller/VehicleControllerTest.java new file mode 100644 index 0000000..0860258 --- /dev/null +++ b/src/test/java/com/crimeLink/analyzer/controller/VehicleControllerTest.java @@ -0,0 +1,124 @@ +package com.crimeLink.analyzer.controller; + +import com.crimeLink.analyzer.entity.Vehicle; +import com.crimeLink.analyzer.service.VehicleService; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.when; +import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.http.MediaType.APPLICATION_PDF; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@ExtendWith(MockitoExtension.class) +class VehicleControllerTest { + + @InjectMocks + private VehicleController controller; + + @Mock + private VehicleService vehicleService; + + private MockMvc mockMvc; + private final ObjectMapper mapper = new ObjectMapper().findAndRegisterModules(); + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(controller).build(); + } + + @Test + void getVehicles_shouldReturn200() throws Exception { + when(vehicleService.getAllVehicles()).thenReturn(List.of(new Vehicle())); + mockMvc.perform(get("/api/vehicles")) + .andExpect(status().isOk()); + } + + @Test + void addVehicle_shouldReturn201() throws Exception { + Vehicle v = new Vehicle(); + v.setId(1L); + v.setNumberPlate("CAB-1234"); + + when(vehicleService.addVehicle(any(Vehicle.class))).thenReturn(v); + + mockMvc.perform(post("/api/vehicles") + .contentType(APPLICATION_JSON) + .content(mapper.writeValueAsString(v))) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.id").value(1)); + } + + @Test + void getVehicleById_shouldReturn200_whenFound() throws Exception { + Vehicle v = new Vehicle(); + v.setId(2L); + + when(vehicleService.getVehicleById(2L)).thenReturn(v); + + mockMvc.perform(get("/api/vehicles/2")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(2)); + } + + @Test + void getVehicleById_shouldReturn404_whenMissing() throws Exception { + when(vehicleService.getVehicleById(2L)).thenReturn(null); + + mockMvc.perform(get("/api/vehicles/2")) + .andExpect(status().isNotFound()); + } + + @Test + void deleteVehicle_shouldReturn204() throws Exception { + mockMvc.perform(delete("/api/vehicles/2")) + .andExpect(status().isNoContent()); + } + + @Test + void updateVehicle_shouldReturn200_whenFound() throws Exception { + Vehicle v = new Vehicle(); + v.setId(2L); + + when(vehicleService.updateVehicle(anyLong(), any(Vehicle.class))).thenReturn(v); + + mockMvc.perform(put("/api/vehicles/2") + .contentType(APPLICATION_JSON) + .content(mapper.writeValueAsString(v))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(2)); + } + + @Test + void updateVehicle_shouldReturn404_whenMissing() throws Exception { + when(vehicleService.updateVehicle(anyLong(), any(Vehicle.class))).thenReturn(null); + + mockMvc.perform(put("/api/vehicles/2") + .contentType(APPLICATION_JSON) + .content(mapper.writeValueAsString(new Vehicle()))) + .andExpect(status().isNotFound()); + } + + @Test + void getPlateRegistryReportPdf_shouldReturnPdf() throws Exception { + when(vehicleService.generatePlateRegistryReportPdf("2026-03-01", "2026-03-31")).thenReturn("pdf".getBytes()); + + mockMvc.perform(get("/api/vehicles/report/pdf") + .param("start", "2026-03-01") + .param("end", "2026-03-31")) + .andExpect(status().isOk()) + .andExpect(content().contentType(APPLICATION_PDF)); + } +} \ No newline at end of file diff --git a/src/test/java/com/crimeLink/analyzer/service/AuditServiceTest.java b/src/test/java/com/crimeLink/analyzer/service/AuditServiceTest.java new file mode 100644 index 0000000..9a08c8a --- /dev/null +++ b/src/test/java/com/crimeLink/analyzer/service/AuditServiceTest.java @@ -0,0 +1,77 @@ +package com.crimeLink.analyzer.service; + +import com.crimeLink.analyzer.entity.LoginAudit; +import com.crimeLink.analyzer.repository.LoginAuditRepository; +import jakarta.servlet.http.HttpServletRequest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.*; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; + +import java.time.LocalDateTime; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class AuditServiceTest { + + @Mock + private LoginAuditRepository loginAuditRepository; + + @Mock + private HttpServletRequest request; + + @InjectMocks + private AuditService service; + + @Test + void logLoginAttempt_shouldUseForwardedIp_whenPresent() { + ReflectionTestUtils.setField(service, "loginAuditRepository", loginAuditRepository); + + when(request.getHeader("X-Forwarded-For")).thenReturn("10.0.0.1, 10.0.0.2"); + when(request.getHeader("User-Agent")).thenReturn("JUnit"); + when(loginAuditRepository.save(any(LoginAudit.class))).thenAnswer(inv -> inv.getArgument(0)); + + service.logLoginAttempt(1, "u@test.com", false, "bad password", request); + + ArgumentCaptor captor = ArgumentCaptor.forClass(LoginAudit.class); + verify(loginAuditRepository).save(captor.capture()); + + LoginAudit saved = captor.getValue(); + assertEquals("10.0.0.1", saved.getIpAddress()); + assertEquals("JUnit", saved.getUserAgent()); + assertEquals("u@test.com", saved.getEmail()); + assertEquals(false, saved.getSuccess()); + } + + @Test + void logLoginAttempt_shouldUseRemoteAddr_whenNoForwardedHeader() { + ReflectionTestUtils.setField(service, "loginAuditRepository", loginAuditRepository); + + when(request.getHeader("X-Forwarded-For")).thenReturn(null); + when(request.getHeader("User-Agent")).thenReturn("JUnit"); + when(request.getRemoteAddr()).thenReturn("127.0.0.1"); + + service.logLoginAttempt(1, "u@test.com", true, null, request); + + ArgumentCaptor captor = ArgumentCaptor.forClass(LoginAudit.class); + verify(loginAuditRepository).save(captor.capture()); + + assertEquals("127.0.0.1", captor.getValue().getIpAddress()); + } + + @Test + void countFailedLoginAttempts_shouldDelegateToRepository() { + ReflectionTestUtils.setField(service, "loginAuditRepository", loginAuditRepository); + + when(loginAuditRepository.countBySuccessFalseAndEmailAndLoginTimeAfter(eq("u@test.com"), any(LocalDateTime.class))) + .thenReturn(3L); + + long count = service.countFailedLoginAttempts("u@test.com", 15); + + assertEquals(3L, count); + } +} \ No newline at end of file diff --git a/src/test/java/com/crimeLink/analyzer/service/AuthServiceTest.java b/src/test/java/com/crimeLink/analyzer/service/AuthServiceTest.java new file mode 100644 index 0000000..e45cd97 --- /dev/null +++ b/src/test/java/com/crimeLink/analyzer/service/AuthServiceTest.java @@ -0,0 +1,171 @@ +package com.crimeLink.analyzer.service; + +import com.crimeLink.analyzer.dto.LoginRequest; +import com.crimeLink.analyzer.dto.LoginResponse; +import com.crimeLink.analyzer.entity.RefreshToken; +import com.crimeLink.analyzer.entity.User; +import com.crimeLink.analyzer.repository.UserRepository; +import jakarta.servlet.http.HttpServletRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.*; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith(org.mockito.junit.jupiter.MockitoExtension.class) +class AuthServiceTest { + + @InjectMocks + private AuthService authService; + + @Mock + private UserRepository userRepository; + @Mock + private PasswordEncoder passwordEncoder; + @Mock + private JwtService jwtService; + @Mock + private RefreshTokenService refreshTokenService; + @Mock + private AuditService auditService; + @Mock + private HttpServletRequest httpServletRequest; + + private User activeUser; + + @BeforeEach + void setUp() { + activeUser = new User(); + activeUser.setUserId(1); + activeUser.setName("Test User"); + activeUser.setEmail("test@email.com"); + activeUser.setPasswordHash("$2a$10$hashedPassword"); + activeUser.setStatus("Active"); + activeUser.setRole("Admin"); + ReflectionTestUtils.setField(authService, "userRepository", userRepository); + ReflectionTestUtils.setField(authService, "passwordEncoder", passwordEncoder); + ReflectionTestUtils.setField(authService, "jwtService", jwtService); + ReflectionTestUtils.setField(authService, "refreshTokenService", refreshTokenService); + ReflectionTestUtils.setField(authService, "auditService", auditService); + } + + @Test + void login_shouldReturnFailure_whenUserNotFound() { + LoginRequest request = new LoginRequest(); + request.setEmail("missing@email.com"); + request.setPassword("123456"); + + when(userRepository.findByEmail("missing@email.com")).thenReturn(Optional.empty()); + + LoginResponse response = authService.login(request, httpServletRequest); + + assertFalse(response.isSuccess()); + assertEquals("Invalid email or password", response.getMessage()); + verify(auditService).logLoginAttempt(null, "missing@email.com", false, "User not found", httpServletRequest); + } + + @Test + void login_shouldReturnFailure_whenUserInactive() { + LoginRequest request = new LoginRequest(); + request.setEmail("test@email.com"); + request.setPassword("123456"); + + activeUser.setStatus("Inactive"); + when(userRepository.findByEmail("test@email.com")).thenReturn(Optional.of(activeUser)); + + LoginResponse response = authService.login(request, httpServletRequest); + + assertFalse(response.isSuccess()); + assertEquals("Account is not active", response.getMessage()); + verify(auditService).logLoginAttempt(activeUser.getUserId(), "test@email.com", false, "Account not active", httpServletRequest); + } + + @Test + void login_shouldReturnFailure_whenHashedPasswordInvalid() { + LoginRequest request = new LoginRequest(); + request.setEmail("test@email.com"); + request.setPassword("wrong"); + + when(userRepository.findByEmail("test@email.com")).thenReturn(Optional.of(activeUser)); + when(passwordEncoder.matches("wrong", activeUser.getPasswordHash())).thenReturn(false); + + LoginResponse response = authService.login(request, httpServletRequest); + + assertFalse(response.isSuccess()); + assertEquals("Invalid email or password", response.getMessage()); + verify(auditService).logLoginAttempt(activeUser.getUserId(), "test@email.com", false, "Invalid password", httpServletRequest); + } + + @Test + void login_shouldReturnSuccess_whenHashedPasswordValid() { + LoginRequest request = new LoginRequest(); + request.setEmail("test@email.com"); + request.setPassword("correct"); + + RefreshToken refreshToken = new RefreshToken(); + refreshToken.setToken("refresh-token"); + + when(userRepository.findByEmail("test@email.com")).thenReturn(Optional.of(activeUser)); + when(passwordEncoder.matches("correct", activeUser.getPasswordHash())).thenReturn(true); + when(jwtService.generateToken(activeUser)).thenReturn("access-token"); + when(refreshTokenService.createRefreshToken(activeUser.getUserId())).thenReturn(refreshToken); + + LoginResponse response = authService.login(request, httpServletRequest); + + assertTrue(response.isSuccess()); + assertEquals("Login successful", response.getMessage()); + assertEquals("access-token", response.getAccessToken()); + assertEquals("refresh-token", response.getRefreshToken()); + assertNotNull(response.getUser()); + assertEquals(activeUser.getUserId(), response.getUser().getUserId()); + verify(auditService).logLoginAttempt(activeUser.getUserId(), "test@email.com", true, null, httpServletRequest); + } + + @Test + void login_shouldMigratePlainTextPassword_whenMatched() { + LoginRequest request = new LoginRequest(); + request.setEmail("plain@email.com"); + request.setPassword("plain123"); + + activeUser.setEmail("plain@email.com"); + activeUser.setPasswordHash("plain123"); + + RefreshToken refreshToken = new RefreshToken(); + refreshToken.setToken("refresh-token"); + + when(userRepository.findByEmail("plain@email.com")).thenReturn(Optional.of(activeUser)); + when(passwordEncoder.encode("plain123")).thenReturn("$2a$10$newHash"); + when(jwtService.generateToken(activeUser)).thenReturn("access-token"); + when(refreshTokenService.createRefreshToken(activeUser.getUserId())).thenReturn(refreshToken); + + LoginResponse response = authService.login(request, httpServletRequest); + + assertTrue(response.isSuccess()); + assertEquals("$2a$10$newHash", activeUser.getPasswordHash()); + verify(userRepository).save(activeUser); + } + + @Test + void login_shouldFail_whenPlainTextPasswordNotMatched() { + LoginRequest request = new LoginRequest(); + request.setEmail("plain@email.com"); + request.setPassword("wrong"); + + activeUser.setEmail("plain@email.com"); + activeUser.setPasswordHash("plain123"); + + when(userRepository.findByEmail("plain@email.com")).thenReturn(Optional.of(activeUser)); + + LoginResponse response = authService.login(request, httpServletRequest); + + assertFalse(response.isSuccess()); + assertEquals("Invalid email or password", response.getMessage()); + verify(userRepository, never()).save(any()); + } +} \ No newline at end of file diff --git a/src/test/java/com/crimeLink/analyzer/service/CallAnalysisServiceTest.java b/src/test/java/com/crimeLink/analyzer/service/CallAnalysisServiceTest.java new file mode 100644 index 0000000..90ecb0d --- /dev/null +++ b/src/test/java/com/crimeLink/analyzer/service/CallAnalysisServiceTest.java @@ -0,0 +1,103 @@ +package com.crimeLink.analyzer.service; + +import com.fasterxml.jackson.databind.JsonNode; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentMatchers; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.*; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +import java.io.IOException; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class CallAnalysisServiceTest { + + @Mock + private RestTemplate restTemplate; + + private CallAnalysisService service; + + @BeforeEach + void setUp() { + service = new CallAnalysisService(restTemplate); + ReflectionTestUtils.setField(service, "callAnalysisServiceUrl", "http://localhost:5001"); + } + + @Test + void analyzeCallRecord_shouldReturnJson() { + MockMultipartFile file = new MockMultipartFile("file", "a.pdf", "application/pdf", "abc".getBytes()); + + when(restTemplate.exchange( + eq("http://localhost:5001/analyze"), + eq(HttpMethod.POST), + any(HttpEntity.class), + eq(String.class) + )).thenReturn(ResponseEntity.ok("{\"result\":\"ok\"}")); + + JsonNode result = service.analyzeCallRecord(file); + + assertEquals("ok", result.get("result").asText()); + } + + @Test + void analyzeCallRecord_shouldThrow_whenRestFails() { + MockMultipartFile file = new MockMultipartFile("file", "a.pdf", "application/pdf", "abc".getBytes()); + + when(restTemplate.exchange( + eq("http://localhost:5001/analyze"), + eq(HttpMethod.POST), + any(HttpEntity.class), + eq(String.class) + )).thenThrow(new RestClientException("down")); + + assertThrows(RuntimeException.class, () -> service.analyzeCallRecord(file)); + } + + @Test + void analyzeBatch_shouldReturnJson() { + MockMultipartFile f1 = new MockMultipartFile("files", "a.pdf", "application/pdf", "abc".getBytes()); + + when(restTemplate.exchange( + eq("http://localhost:5001/analyze/batch"), + eq(HttpMethod.POST), + any(HttpEntity.class), + eq(String.class) + )).thenReturn(ResponseEntity.ok("{\"batch\":\"ok\"}")); + + JsonNode result = service.analyzeBatch(List.of(f1)); + + assertEquals("ok", result.get("batch").asText()); + } + + @Test + void checkHealth_shouldReturnHealthyNode() { + when(restTemplate.getForEntity("http://localhost:5001/health", String.class)) + .thenReturn(ResponseEntity.ok("{\"status\":\"healthy\"}")); + + JsonNode result = service.checkHealth(); + + assertEquals("healthy", result.get("status").asText()); + } + + @Test + void checkHealth_shouldReturnUnhealthyNode_whenRestFails() { + when(restTemplate.getForEntity("http://localhost:5001/health", String.class)) + .thenThrow(new RestClientException("down")); + + JsonNode result = service.checkHealth(); + + assertEquals("unhealthy", result.get("status").asText()); + } +} \ No newline at end of file diff --git a/src/test/java/com/crimeLink/analyzer/service/CustomUserDetailsServiceTest.java b/src/test/java/com/crimeLink/analyzer/service/CustomUserDetailsServiceTest.java new file mode 100644 index 0000000..ab4f4fa --- /dev/null +++ b/src/test/java/com/crimeLink/analyzer/service/CustomUserDetailsServiceTest.java @@ -0,0 +1,51 @@ +package com.crimeLink.analyzer.service; + +import com.crimeLink.analyzer.entity.User; +import com.crimeLink.analyzer.repository.UserRepository; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.when; + +@ExtendWith(org.mockito.junit.jupiter.MockitoExtension.class) +class CustomUserDetailsServiceTest { + + @InjectMocks + private CustomUserDetailsService service; + + @Mock + private UserRepository userRepository; + + @Test + void loadUserByUsername_shouldReturnUserDetails() { + User user = new User(); + user.setEmail("user@test.com"); + user.setPasswordHash("pw"); + user.setRole("Admin"); + user.setStatus("Active"); + + ReflectionTestUtils.setField(service, "userRepository", userRepository); + when(userRepository.findByEmail("user@test.com")).thenReturn(Optional.of(user)); + + UserDetails result = service.loadUserByUsername("user@test.com"); + + assertEquals("user@test.com", result.getUsername()); + } + + @Test + void loadUserByUsername_shouldThrow_whenNotFound() { + ReflectionTestUtils.setField(service, "userRepository", userRepository); + when(userRepository.findByEmail("missing@test.com")).thenReturn(Optional.empty()); + + assertThrows(UsernameNotFoundException.class, + () -> service.loadUserByUsername("missing@test.com")); + } +} \ No newline at end of file diff --git a/src/test/java/com/crimeLink/analyzer/service/DutyRecommendationServiceTest.java b/src/test/java/com/crimeLink/analyzer/service/DutyRecommendationServiceTest.java new file mode 100644 index 0000000..6d761f1 --- /dev/null +++ b/src/test/java/com/crimeLink/analyzer/service/DutyRecommendationServiceTest.java @@ -0,0 +1,113 @@ +package com.crimeLink.analyzer.service; + +import com.crimeLink.analyzer.dto.DutyRecommendationRequest; +import com.crimeLink.analyzer.dto.OfficerRecommendationDTO; +import com.crimeLink.analyzer.entity.OfficerPerformance; +import com.crimeLink.analyzer.entity.User; +import com.crimeLink.analyzer.repository.OfficerPerformanceRepository; +import com.crimeLink.analyzer.repository.UserRepository; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import java.time.LocalDate; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.when; + +@ExtendWith(org.mockito.junit.jupiter.MockitoExtension.class) +class DutyRecommendationServiceTest { + + @Mock + private UserRepository userRepo; + + @Mock + private OfficerPerformanceRepository performanceRepo; + + @InjectMocks + private DutyRecommendationService service; + + @Test + void getOfficerRecommendations_shouldReturnRankedList() { + User officer1 = new User(); + officer1.setUserId(1); + officer1.setName("Officer A"); + + User officer2 = new User(); + officer2.setUserId(2); + officer2.setName("Officer B"); + + OfficerPerformance perf1 = OfficerPerformance.builder() + .officer(officer1) + .totalDuties(3) + .lastDutyDate(LocalDate.now().minusDays(8)) + .reliabilityScore(90) + .build(); + + OfficerPerformance perf2 = OfficerPerformance.builder() + .officer(officer2) + .totalDuties(20) + .lastDutyDate(LocalDate.now().minusDays(1)) + .reliabilityScore(60) + .build(); + + DutyRecommendationRequest req = new DutyRecommendationRequest(); + req.setDate(LocalDate.now()); + req.setRequiredOfficers(2); + + when(userRepo.findByRoleAndStatus("FieldOfficer", "Active")) + .thenReturn(List.of(officer1, officer2)); + when(performanceRepo.findByOfficer_UserId(1)).thenReturn(List.of(perf1)); + when(performanceRepo.findByOfficer_UserId(2)).thenReturn(List.of(perf2)); + + List result = service.getOfficerRecommendations(req); + + assertEquals(2, result.size()); + assertEquals(1, result.get(0).getOfficerId()); + assertTrue(result.get(0).getRecommendationScore() >= result.get(1).getRecommendationScore()); + } + + @Test + void getOfficerRecommendations_shouldUseDefaultValues_whenNoPerformanceData() { + User officer = new User(); + officer.setUserId(1); + officer.setName("Officer A"); + + DutyRecommendationRequest req = new DutyRecommendationRequest(); + req.setDate(LocalDate.now()); + + when(userRepo.findByRoleAndStatus("FieldOfficer", "Active")) + .thenReturn(List.of(officer)); + when(performanceRepo.findByOfficer_UserId(1)).thenReturn(List.of()); + + List result = service.getOfficerRecommendations(req); + + assertEquals(1, result.size()); + assertEquals("Unknown", result.get(0).getAvailabilityStatus()); + assertEquals(0, result.get(0).getTotalDuties()); + assertNotNull(result.get(0).getReason()); + } + + @Test + void getOfficerRecommendations_shouldLimitResults_whenRequiredOfficerCountProvided() { + User officer1 = new User(); officer1.setUserId(1); officer1.setName("A"); + User officer2 = new User(); officer2.setUserId(2); officer2.setName("B"); + User officer3 = new User(); officer3.setUserId(3); officer3.setName("C"); + + DutyRecommendationRequest req = new DutyRecommendationRequest(); + req.setDate(LocalDate.now()); + req.setRequiredOfficers(2); + + when(userRepo.findByRoleAndStatus("FieldOfficer", "Active")) + .thenReturn(List.of(officer1, officer2, officer3)); + when(performanceRepo.findByOfficer_UserId(1)).thenReturn(List.of()); + when(performanceRepo.findByOfficer_UserId(2)).thenReturn(List.of()); + when(performanceRepo.findByOfficer_UserId(3)).thenReturn(List.of()); + + List result = service.getOfficerRecommendations(req); + + assertEquals(2, result.size()); + } +} \ No newline at end of file diff --git a/src/test/java/com/crimeLink/analyzer/service/DutyScheduleServiceTest.java b/src/test/java/com/crimeLink/analyzer/service/DutyScheduleServiceTest.java new file mode 100644 index 0000000..fdd27cb --- /dev/null +++ b/src/test/java/com/crimeLink/analyzer/service/DutyScheduleServiceTest.java @@ -0,0 +1,219 @@ +package com.crimeLink.analyzer.service; + +import com.crimeLink.analyzer.dto.DutyScheduleRequest; +import com.crimeLink.analyzer.dto.OfficerDutyRowDTO; +import com.crimeLink.analyzer.entity.DutySchedule; +import com.crimeLink.analyzer.entity.OfficerPerformance; +import com.crimeLink.analyzer.entity.User; +import com.crimeLink.analyzer.enums.DutyStatus; +import com.crimeLink.analyzer.repository.DutyScheduleRepository; +import com.crimeLink.analyzer.repository.OfficerPerformanceRepository; +import com.crimeLink.analyzer.repository.UserRepository; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.*; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith(org.mockito.junit.jupiter.MockitoExtension.class) +class DutyScheduleServiceTest { + + @Mock + private DutyScheduleRepository dutyRepo; + @Mock + private UserRepository userRepo; + @Mock + private OfficerPerformanceRepository performanceRepo; + + @InjectMocks + private DutyScheduleService service; + + @Test + void getOfficerRowsForDate_shouldReturnEmptyRow_whenNoDutiesForOfficer() { + User officer = new User(); + officer.setUserId(1); + officer.setName("Officer A"); + + when(userRepo.findByRoleAndStatus("FieldOfficer", "Active")).thenReturn(List.of(officer)); + when(dutyRepo.findByDate(LocalDate.of(2026, 3, 13))).thenReturn(List.of()); + + List rows = service.getOfficerRowsForDate(LocalDate.of(2026, 3, 13)); + + assertEquals(1, rows.size()); + assertEquals("Officer A", rows.get(0).getOfficerName()); + assertEquals("", rows.get(0).getLocation()); + } + + @Test + void createDuty_shouldThrow_whenOfficerIdMissing() { + DutyScheduleRequest req = new DutyScheduleRequest(); + req.setDate(LocalDate.now()); + req.setStatus(DutyStatus.Active); + + assertThrows(IllegalArgumentException.class, () -> service.createDuty(req)); + } + + @Test + void createDuty_shouldThrow_whenDateMissing() { + DutyScheduleRequest req = new DutyScheduleRequest(); + req.setOfficerId(1); + req.setStatus(DutyStatus.Active); + + assertThrows(IllegalArgumentException.class, () -> service.createDuty(req)); + } + + @Test + void createDuty_shouldThrow_whenStatusMissing() { + DutyScheduleRequest req = new DutyScheduleRequest(); + req.setOfficerId(1); + req.setDate(LocalDate.now()); + + assertThrows(IllegalArgumentException.class, () -> service.createDuty(req)); + } + + @Test + void createDuty_shouldThrow_whenOfficerNotFound() { + DutyScheduleRequest req = new DutyScheduleRequest(); + req.setOfficerId(1); + req.setDate(LocalDate.now()); + req.setStatus(DutyStatus.Active); + + when(userRepo.findById(1)).thenReturn(Optional.empty()); + + assertThrows(RuntimeException.class, () -> service.createDuty(req)); + } + + @Test + void createDuty_shouldThrow_whenNonAbsentAndTimeRangeMissing() { + User officer = new User(); + officer.setUserId(1); + + DutyScheduleRequest req = new DutyScheduleRequest(); + req.setOfficerId(1); + req.setDate(LocalDate.now()); + req.setStatus(DutyStatus.Active); + + when(userRepo.findById(1)).thenReturn(Optional.of(officer)); + + assertThrows(IllegalArgumentException.class, () -> service.createDuty(req)); + } + + @Test + void createDuty_shouldCreateAbsentDuty_withoutTimeRange() { + User officer = new User(); + officer.setUserId(1); + + DutySchedule saved = new DutySchedule(); + saved.setAssignedOfficer(officer); + saved.setDate(LocalDate.now()); + saved.setStatus(DutyStatus.Absent); + + DutyScheduleRequest req = new DutyScheduleRequest(); + req.setOfficerId(1); + req.setDate(LocalDate.now()); + req.setStatus(DutyStatus.Absent); + req.setDescription("Absent"); + + when(userRepo.findById(1)).thenReturn(Optional.of(officer)); + when(dutyRepo.findByDateAndAssignedOfficer_UserId(req.getDate(), 1)).thenReturn(Optional.empty()); + when(dutyRepo.save(any(DutySchedule.class))).thenReturn(saved); + + DutySchedule result = service.createDuty(req); + + assertEquals(DutyStatus.Absent, result.getStatus()); + verify(performanceRepo, never()).save(any()); + } + + @Test + void createDuty_shouldCreateActiveDuty_andUpdatePerformance() { + User officer = new User(); + officer.setUserId(1); + officer.setName("Officer A"); + + DutySchedule saved = new DutySchedule(); + saved.setAssignedOfficer(officer); + saved.setDate(LocalDate.now()); + saved.setStatus(DutyStatus.Active); + saved.setTimeRange("08:00-12:00"); + saved.setLocation("Matara"); + + DutyScheduleRequest req = new DutyScheduleRequest(); + req.setOfficerId(1); + req.setDate(LocalDate.now()); + req.setStatus(DutyStatus.Active); + req.setTimeRange("08:00-12:00"); + req.setLocation("Matara"); + + when(userRepo.findById(1)).thenReturn(Optional.of(officer)); + when(dutyRepo.findByDateAndAssignedOfficer_UserIdAndTimeRange(req.getDate(), 1, "08:00-12:00")) + .thenReturn(Optional.empty()); + when(dutyRepo.save(any(DutySchedule.class))).thenReturn(saved); + when(performanceRepo.findByOfficer_UserId(1)).thenReturn(List.of()); + + DutySchedule result = service.createDuty(req); + + assertEquals(DutyStatus.Active, result.getStatus()); + verify(performanceRepo).save(any(OfficerPerformance.class)); + } + + @Test + void updateDutyStatus_shouldUpdateExistingDuty() { + DutySchedule duty = new DutySchedule(); + duty.setId(10L); + duty.setStatus(DutyStatus.Active); + + when(dutyRepo.findById(10L)).thenReturn(Optional.of(duty)); + + service.updateDutyStatus(10L, DutyStatus.Completed); + + assertEquals(DutyStatus.Completed, duty.getStatus()); + verify(dutyRepo).save(duty); + } + + @Test + void getDutyLocations_shouldReturnDefault_whenDatabaseEmpty() { + when(dutyRepo.findDistinctLocations()).thenReturn(List.of()); + + List result = service.getDutyLocations(); + + assertFalse(result.isEmpty()); + assertTrue(result.contains("Matara")); + } + + @Test + void getDutiesBetween_shouldReturnRepositoryData() { + when(dutyRepo.findByDateBetween(LocalDate.of(2026, 3, 1), LocalDate.of(2026, 3, 31))) + .thenReturn(List.of(new DutySchedule())); + + List result = service.getDutiesBetween(LocalDate.of(2026, 3, 1), LocalDate.of(2026, 3, 31)); + + assertEquals(1, result.size()); + } + + @Test + void generateDutyScheduleReportPdf_shouldReturnBytes() { + User officer = new User(); + officer.setName("Officer A"); + + DutySchedule duty = new DutySchedule(); + duty.setDate(LocalDate.of(2026, 3, 13)); + duty.setAssignedOfficer(officer); + duty.setLocation("Matara"); + duty.setTimeRange("08:00-12:00"); + duty.setStatus(DutyStatus.Active); + duty.setDescription("Morning shift"); + + when(dutyRepo.findByDateBetween(LocalDate.of(2026, 3, 1), LocalDate.of(2026, 3, 31))) + .thenReturn(List.of(duty)); + + byte[] pdf = service.generateDutyScheduleReportPdf(LocalDate.of(2026, 3, 1), LocalDate.of(2026, 3, 31)); + + assertNotNull(pdf); + assertTrue(pdf.length > 0); + } +} \ No newline at end of file diff --git a/src/test/java/com/crimeLink/analyzer/service/FacialRecognitionServiceTest.java b/src/test/java/com/crimeLink/analyzer/service/FacialRecognitionServiceTest.java new file mode 100644 index 0000000..d2bbae6 --- /dev/null +++ b/src/test/java/com/crimeLink/analyzer/service/FacialRecognitionServiceTest.java @@ -0,0 +1,115 @@ +package com.crimeLink.analyzer.service; + +import com.fasterxml.jackson.databind.JsonNode; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class FacialRecognitionServiceTest { + + @Mock + private RestTemplate restTemplate; + + private FacialRecognitionService service; + + @BeforeEach + void setUp() { + service = new FacialRecognitionService(restTemplate); + ReflectionTestUtils.setField(service, "facialRecognitionServiceUrl", "http://localhost:5002"); + } + + @Test + void analyzeImage_shouldReturnJson() { + MockMultipartFile image = new MockMultipartFile("image", "a.jpg", "image/jpeg", "abc".getBytes()); + + when(restTemplate.exchange( + eq("http://localhost:5002/analyze"), + eq(HttpMethod.POST), + any(HttpEntity.class), + eq(String.class) + )).thenReturn(ResponseEntity.ok("{\"result\":\"ok\"}")); + + JsonNode result = service.analyzeImage(image, 45f, "user-1", null); + + assertEquals("ok", result.get("result").asText()); + } + + @Test + void registerCriminal_shouldReturnJson() { + MockMultipartFile photo = new MockMultipartFile("photo", "a.jpg", "image/jpeg", "abc".getBytes()); + + when(restTemplate.exchange( + eq("http://localhost:5002/register"), + eq(HttpMethod.POST), + any(HttpEntity.class), + eq(String.class) + )).thenReturn(ResponseEntity.ok("{\"saved\":\"yes\"}")); + + JsonNode result = service.registerCriminal( + photo, null, "John", "123", "medium", + null, null, null, null, null, null, null, "active" + ); + + assertEquals("yes", result.get("saved").asText()); + } + + @Test + void getCriminals_shouldReturnJson() { + when(restTemplate.getForEntity("http://localhost:5002/criminals", String.class)) + .thenReturn(ResponseEntity.ok("{\"count\":1}")); + + JsonNode result = service.getCriminals(); + + assertEquals(1, result.get("count").asInt()); + } + + @Test + void getRecognitionHistory_shouldReturnJson() { + when(restTemplate.getForEntity("http://localhost:5002/history?limit=10", String.class)) + .thenReturn(ResponseEntity.ok("{\"history\":\"ok\"}")); + + JsonNode result = service.getRecognitionHistory(10); + + assertEquals("ok", result.get("history").asText()); + } + + @Test + void generateEmbedding_shouldReturnJson() { + MockMultipartFile photo = new MockMultipartFile("photo", "a.jpg", "image/jpeg", "abc".getBytes()); + + when(restTemplate.exchange( + eq("http://localhost:5002/generate-embedding"), + eq(HttpMethod.POST), + any(HttpEntity.class), + eq(String.class) + )).thenReturn(ResponseEntity.ok("{\"embedding\":\"done\"}")); + + JsonNode result = service.generateEmbedding("c1", photo); + + assertEquals("done", result.get("embedding").asText()); + } + + @Test + void checkHealth_shouldReturnUnhealthy_whenRestFails() { + when(restTemplate.getForEntity("http://localhost:5002/health", String.class)) + .thenThrow(new RestClientException("down")); + + JsonNode result = service.checkHealth(); + + assertEquals("unhealthy", result.get("status").asText()); + } +} \ No newline at end of file diff --git a/src/test/java/com/crimeLink/analyzer/service/LeaveServiceTest.java b/src/test/java/com/crimeLink/analyzer/service/LeaveServiceTest.java new file mode 100644 index 0000000..63236a6 --- /dev/null +++ b/src/test/java/com/crimeLink/analyzer/service/LeaveServiceTest.java @@ -0,0 +1,216 @@ +package com.crimeLink.analyzer.service; + +import com.crimeLink.analyzer.dto.LeaveRequestDTO; +import com.crimeLink.analyzer.dto.LeaveSubmitRequest; +import com.crimeLink.analyzer.dto.LeaveUpdateRequest; +import com.crimeLink.analyzer.entity.LeaveRequest; +import com.crimeLink.analyzer.entity.User; +import com.crimeLink.analyzer.enums.LeaveStatus; +import com.crimeLink.analyzer.repository.LeaveRequestRepository; +import com.crimeLink.analyzer.repository.UserRepository; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith(org.mockito.junit.jupiter.MockitoExtension.class) +class LeaveServiceTest { + + @Mock + private LeaveRequestRepository leaveRequestRepository; + + @Mock + private UserRepository userRepository; + + @InjectMocks + private LeaveService leaveService; + + @Test + void submitLeaveRequest_shouldThrow_whenUserNotFound() { + LeaveSubmitRequest request = new LeaveSubmitRequest(); + request.setOfficerId(1L); + request.setDate(LocalDate.now()); + request.setReason("Medical"); + + when(userRepository.findById(1)).thenReturn(Optional.empty()); + + assertThrows(RuntimeException.class, () -> leaveService.submitLeaveRequest(request)); + } + + @Test + void submitLeaveRequest_shouldThrow_whenMonthlyLimitExceeded() { + User user = new User(); + user.setUserId(1); + + LeaveSubmitRequest request = new LeaveSubmitRequest(); + request.setOfficerId(1L); + request.setDate(LocalDate.of(2026, 3, 13)); + request.setReason("Medical"); + + when(userRepository.findById(1)).thenReturn(Optional.of(user)); + when(leaveRequestRepository.countMonthlyLeaves(1, 2026, 3)).thenReturn(5); + + assertThrows(RuntimeException.class, () -> leaveService.submitLeaveRequest(request)); + } + + @Test + void submitLeaveRequest_shouldThrow_whenDuplicateExists() { + User user = new User(); + user.setUserId(1); + + LeaveSubmitRequest request = new LeaveSubmitRequest(); + request.setOfficerId(1L); + request.setDate(LocalDate.of(2026, 3, 13)); + request.setReason("Medical"); + + when(userRepository.findById(1)).thenReturn(Optional.of(user)); + when(leaveRequestRepository.countMonthlyLeaves(1, 2026, 3)).thenReturn(2); + when(leaveRequestRepository.existsByUser_UserIdAndDateAndStatusNot(1, LocalDate.of(2026, 3, 13), LeaveStatus.DENIED)) + .thenReturn(true); + + assertThrows(RuntimeException.class, () -> leaveService.submitLeaveRequest(request)); + } + + @Test + void submitLeaveRequest_shouldSave_whenValid() { + User user = new User(); + user.setUserId(1); + user.setName("Officer A"); + + LeaveSubmitRequest request = new LeaveSubmitRequest(); + request.setOfficerId(1L); + request.setDate(LocalDate.of(2026, 3, 13)); + request.setReason("Medical"); + + LeaveRequest saved = new LeaveRequest(); + saved.setId(100L); + saved.setUser(user); + saved.setDate(request.getDate()); + saved.setReason(request.getReason()); + saved.setStatus(LeaveStatus.PENDING); + + when(userRepository.findById(1)).thenReturn(Optional.of(user)); + when(leaveRequestRepository.countMonthlyLeaves(1, 2026, 3)).thenReturn(1); + when(leaveRequestRepository.existsByUser_UserIdAndDateAndStatusNot(1, LocalDate.of(2026, 3, 13), LeaveStatus.DENIED)) + .thenReturn(false); + when(leaveRequestRepository.save(any(LeaveRequest.class))).thenReturn(saved); + + LeaveRequestDTO result = leaveService.submitLeaveRequest(request); + + assertEquals(100L, result.getId()); + assertEquals("PENDING", result.getStatus()); + assertEquals("Officer A", result.getOfficerName()); + } + + @Test + void getOfficerLeaves_shouldReturnLeaveList() { + User user = new User(); + user.setUserId(1); + user.setName("Officer A"); + + LeaveRequest leave = new LeaveRequest(); + leave.setId(1L); + leave.setUser(user); + leave.setDate(LocalDate.now()); + leave.setReason("Medical"); + leave.setStatus(LeaveStatus.PENDING); + + when(userRepository.findById(1)).thenReturn(Optional.of(user)); + when(leaveRequestRepository.findByUser_UserId(1)).thenReturn(List.of(leave)); + + List result = leaveService.getOfficerLeaves(1L); + + assertEquals(1, result.size()); + assertEquals("Officer A", result.get(0).getOfficerName()); + } + + @Test + void getAllLeaveRequests_shouldReturnMonthData() { + User user = new User(); + user.setUserId(1); + user.setName("Officer A"); + + LeaveRequest leave = new LeaveRequest(); + leave.setId(1L); + leave.setUser(user); + leave.setDate(LocalDate.of(2026, 3, 13)); + leave.setReason("Medical"); + leave.setStatus(LeaveStatus.PENDING); + + when(leaveRequestRepository.findByDateBetween( + LocalDate.of(2026, 3, 1), + LocalDate.of(2026, 3, 31)) + ).thenReturn(List.of(leave)); + + List result = leaveService.getAllLeaveRequests("2026-03"); + + assertEquals(1, result.size()); + } + + @Test + void updateLeaveStatus_shouldThrow_whenLeaveAlreadyProcessed() { + LeaveRequest leave = new LeaveRequest(); + leave.setId(1L); + leave.setStatus(LeaveStatus.APPROVED); + + LeaveUpdateRequest request = new LeaveUpdateRequest(); + request.setStatus("DENIED"); + request.setResponseReason("Not enough staff"); + + when(leaveRequestRepository.findById(1L)).thenReturn(Optional.of(leave)); + + assertThrows(RuntimeException.class, () -> leaveService.updateLeaveStatus(1L, request, 2L)); + } + + @Test + void updateLeaveStatus_shouldThrow_whenDeniedWithoutReason() { + LeaveRequest leave = new LeaveRequest(); + leave.setId(1L); + leave.setStatus(LeaveStatus.PENDING); + + LeaveUpdateRequest request = new LeaveUpdateRequest(); + request.setStatus("DENIED"); + request.setResponseReason(" "); + + when(leaveRequestRepository.findById(1L)).thenReturn(Optional.of(leave)); + + assertThrows(RuntimeException.class, () -> leaveService.updateLeaveStatus(1L, request, 2L)); + } + + @Test + void updateLeaveStatus_shouldUpdateSuccessfully() { + User officer = new User(); + officer.setUserId(1); + officer.setName("Officer A"); + + User responder = new User(); + responder.setUserId(2); + + LeaveRequest leave = new LeaveRequest(); + leave.setId(1L); + leave.setUser(officer); + leave.setStatus(LeaveStatus.PENDING); + leave.setDate(LocalDate.now()); + leave.setReason("Medical"); + + LeaveUpdateRequest request = new LeaveUpdateRequest(); + request.setStatus("APPROVED"); + request.setResponseReason("Approved"); + + when(leaveRequestRepository.findById(1L)).thenReturn(Optional.of(leave)); + when(userRepository.findById(2)).thenReturn(Optional.of(responder)); + when(leaveRequestRepository.save(any(LeaveRequest.class))).thenAnswer(inv -> inv.getArgument(0)); + + LeaveRequestDTO result = leaveService.updateLeaveStatus(1L, request, 2L); + + assertEquals("APPROVED", result.getStatus()); + assertEquals(2L, result.getRespondedBy()); + } +} \ No newline at end of file diff --git a/src/test/java/com/crimeLink/analyzer/service/MobileDutyServiceTest.java b/src/test/java/com/crimeLink/analyzer/service/MobileDutyServiceTest.java new file mode 100644 index 0000000..7bf21af --- /dev/null +++ b/src/test/java/com/crimeLink/analyzer/service/MobileDutyServiceTest.java @@ -0,0 +1,78 @@ +package com.crimeLink.analyzer.service; + +import com.crimeLink.analyzer.dto.DutyAssignmentDTO; +import com.crimeLink.analyzer.dto.DutyDetailDTO; +import com.crimeLink.analyzer.entity.DutySchedule; +import com.crimeLink.analyzer.entity.User; +import com.crimeLink.analyzer.enums.DutyStatus; +import com.crimeLink.analyzer.repository.DutyScheduleRepository; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import java.time.LocalDate; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.when; + +@ExtendWith(org.mockito.junit.jupiter.MockitoExtension.class) +class MobileDutyServiceTest { + + @Mock + private DutyScheduleRepository dutyScheduleRepository; + + @InjectMocks + private MobileDutyService service; + + @Test + void getDutiesByOfficerId_shouldReturnMappedAssignments() { + User officer = new User(); + officer.setUserId(1); + officer.setName("Officer A"); + + DutySchedule duty = new DutySchedule(); + duty.setId(10L); + duty.setAssignedOfficer(officer); + duty.setDate(LocalDate.of(2026, 3, 13)); + duty.setLocation("Matara"); + duty.setTimeRange("08:00-12:00"); + duty.setStatus(DutyStatus.Active); + duty.setDescription("Morning duty"); + + when(dutyScheduleRepository.findByAssignedOfficer_UserId(1L)).thenReturn(List.of(duty)); + + List result = service.getDutiesByOfficerId(1L); + + assertEquals(1, result.size()); + assertEquals("Officer A", result.get(0).getOfficerName()); + assertEquals(DutyStatus.Active.name(), result.get(0).getStatus()); + } + + @Test + void getDutyDetailsByDate_shouldReturnMappedDetails() { + User officer = new User(); + officer.setUserId(1); + officer.setName("Officer A"); + + DutySchedule duty = new DutySchedule(); + duty.setId(10L); + duty.setAssignedOfficer(officer); + duty.setDate(LocalDate.of(2026, 3, 13)); + duty.setLocation(null); + duty.setTimeRange(null); + duty.setStatus(null); + duty.setDescription("Description"); + + when(dutyScheduleRepository.findByDateAndAssignedOfficer_UserId(LocalDate.of(2026, 3, 13), 1L)) + .thenReturn(List.of(duty)); + + List result = service.getDutyDetailsByDate(1L, LocalDate.of(2026, 3, 13)); + + assertEquals(1, result.size()); + assertEquals("N/A", result.get(0).getLocation()); + assertEquals("N/A", result.get(0).getTimeRange()); + assertEquals("PENDING", result.get(0).getStatus()); + } +} \ No newline at end of file diff --git a/src/test/java/com/crimeLink/analyzer/service/RefreshTokenServiceTest.java b/src/test/java/com/crimeLink/analyzer/service/RefreshTokenServiceTest.java new file mode 100644 index 0000000..caa803f --- /dev/null +++ b/src/test/java/com/crimeLink/analyzer/service/RefreshTokenServiceTest.java @@ -0,0 +1,156 @@ +package com.crimeLink.analyzer.service; + +import com.crimeLink.analyzer.entity.RefreshToken; +import com.crimeLink.analyzer.entity.User; +import com.crimeLink.analyzer.repository.RefreshTokenRepository; +import com.crimeLink.analyzer.repository.UserRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.*; +import org.springframework.test.util.ReflectionTestUtils; + +import java.time.LocalDateTime; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith(org.mockito.junit.jupiter.MockitoExtension.class) +class RefreshTokenServiceTest { + + @InjectMocks + private RefreshTokenService service; + + @Mock + private RefreshTokenRepository refreshTokenRepository; + + @Mock + private UserRepository userRepository; + + @BeforeEach + void setUp() { + ReflectionTestUtils.setField(service, "refreshTokenDuration", 3600000L); + ReflectionTestUtils.setField(service, "refreshTokenRepository", refreshTokenRepository); + ReflectionTestUtils.setField(service, "userRepository", userRepository); + } + + @Test + void createRefreshToken_shouldCreateByUserId() { + User user = new User(); + user.setUserId(1); + + when(userRepository.findById(1)).thenReturn(Optional.of(user)); + when(refreshTokenRepository.save(any(RefreshToken.class))).thenAnswer(inv -> inv.getArgument(0)); + + RefreshToken result = service.createRefreshToken(1); + + assertNotNull(result.getToken()); + assertEquals(user, result.getUser()); + assertFalse(result.getRevoked()); + } + + @Test + void findByToken_shouldDelegate() { + RefreshToken token = new RefreshToken(); + when(refreshTokenRepository.findByToken("abc")).thenReturn(Optional.of(token)); + + Optional result = service.findByToken("abc"); + + assertTrue(result.isPresent()); + } + + @Test + void findValidToken_shouldReturnEmpty_whenNotFound() { + when(refreshTokenRepository.findByTokenWithUser("abc")).thenReturn(Optional.empty()); + + Optional result = service.findValidToken("abc"); + + assertTrue(result.isEmpty()); + } + + @Test + void findValidToken_shouldDeleteAndReturnEmpty_whenExpired() { + RefreshToken token = new RefreshToken(); + token.setExpiryDate(LocalDateTime.now().minusMinutes(1)); + token.setRevoked(false); + + when(refreshTokenRepository.findByTokenWithUser("abc")).thenReturn(Optional.of(token)); + + Optional result = service.findValidToken("abc"); + + assertTrue(result.isEmpty()); + verify(refreshTokenRepository).delete(token); + } + + @Test + void findValidToken_shouldReturnEmpty_whenRevoked() { + RefreshToken token = new RefreshToken(); + token.setExpiryDate(LocalDateTime.now().plusMinutes(10)); + token.setRevoked(true); + + when(refreshTokenRepository.findByTokenWithUser("abc")).thenReturn(Optional.of(token)); + + Optional result = service.findValidToken("abc"); + + assertTrue(result.isEmpty()); + } + + @Test + void findValidToken_shouldReturnToken_whenValid() { + RefreshToken token = new RefreshToken(); + token.setExpiryDate(LocalDateTime.now().plusMinutes(10)); + token.setRevoked(false); + + when(refreshTokenRepository.findByTokenWithUser("abc")).thenReturn(Optional.of(token)); + + Optional result = service.findValidToken("abc"); + + assertTrue(result.isPresent()); + } + + @Test + void rotateRefreshToken_shouldRevokeCurrentAndCreateNew() { + User user = new User(); + user.setUserId(1); + + RefreshToken current = new RefreshToken(); + current.setUser(user); + current.setRevoked(false); + + when(refreshTokenRepository.save(any(RefreshToken.class))).thenAnswer(inv -> inv.getArgument(0)); + + RefreshToken spyNew = spy(new RefreshToken()); + spyNew.setUser(user); + spyNew.setToken("new-token"); + + RefreshTokenService spyService = Mockito.spy(service); + doReturn(spyNew).when(spyService).createRefreshToken(user); + + RefreshToken result = spyService.rotateRefreshToken(current); + + assertTrue(current.getRevoked()); + assertEquals("new-token", result.getToken()); + verify(refreshTokenRepository).save(current); + } + + @Test + void deleteByUser_shouldDelegate() { + User user = new User(); + service.deleteByUser(user); + verify(refreshTokenRepository).deleteByUser(user); + } + + @Test + void revokeAllUserTokens_shouldDelegate() { + User user = new User(); + service.revokeAllUserTokens(user); + verify(refreshTokenRepository).revokeAllUserTokens(user); + } + + @Test + void deleteExpiredTokens_shouldDelegate() { + service.deleteExpiredTokens(); + verify(refreshTokenRepository).deleteByExpiryDateBefore(any(LocalDateTime.class)); + } +} \ No newline at end of file diff --git a/src/test/java/com/crimeLink/analyzer/service/SafetyLocationServiceTest.java b/src/test/java/com/crimeLink/analyzer/service/SafetyLocationServiceTest.java new file mode 100644 index 0000000..0955dc2 --- /dev/null +++ b/src/test/java/com/crimeLink/analyzer/service/SafetyLocationServiceTest.java @@ -0,0 +1,37 @@ +package com.crimeLink.analyzer.service; + +import com.crimeLink.analyzer.entity.SafetyLocation; +import com.crimeLink.analyzer.entity.SafetyType; +import com.crimeLink.analyzer.repository.SafetyLocationRepository; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class SafetyLocationServiceTest { + + @Mock + private SafetyLocationRepository repository; + + @InjectMocks + private SafetyLocationService service; + + @Test + void getByType_shouldReturnRepositoryData() { + when(repository.findByType(SafetyType.POLICE)).thenReturn(List.of(new SafetyLocation())); + assertEquals(1, service.getByType(SafetyType.POLICE).size()); + } + + @Test + void getAllLocations_shouldReturnRepositoryData() { + when(repository.findAll()).thenReturn(List.of(new SafetyLocation(), new SafetyLocation())); + assertEquals(2, service.getAllLocations().size()); + } +} \ No newline at end of file diff --git a/src/test/java/com/crimeLink/analyzer/service/UserServiceTest.java b/src/test/java/com/crimeLink/analyzer/service/UserServiceTest.java new file mode 100644 index 0000000..fe7e5c8 --- /dev/null +++ b/src/test/java/com/crimeLink/analyzer/service/UserServiceTest.java @@ -0,0 +1,41 @@ +package com.crimeLink.analyzer.service; + +import com.crimeLink.analyzer.entity.User; +import com.crimeLink.analyzer.repository.UserRepository; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +@ExtendWith(org.mockito.junit.jupiter.MockitoExtension.class) +class UserServiceTest { + + @Mock + private UserRepository repo; + + @InjectMocks + private UserService service; + + @Test + void getFieldOfficers_shouldReturnFieldOfficerList() { + when(repo.findByRole("FieldOfficer")).thenReturn(List.of(new User(), new User())); + + List result = service.getFieldOfficers(); + + assertEquals(2, result.size()); + } + + @Test + void getAllOfficers_shouldReturnAllUsers() { + when(repo.findAll()).thenReturn(List.of(new User())); + + List result = service.getAllOfficers(); + + assertEquals(1, result.size()); + } +} \ No newline at end of file diff --git a/src/test/java/com/crimeLink/analyzer/service/VehicleServiceTest.java b/src/test/java/com/crimeLink/analyzer/service/VehicleServiceTest.java new file mode 100644 index 0000000..5deb070 --- /dev/null +++ b/src/test/java/com/crimeLink/analyzer/service/VehicleServiceTest.java @@ -0,0 +1,110 @@ +package com.crimeLink.analyzer.service; + +import com.crimeLink.analyzer.entity.Vehicle; +import com.crimeLink.analyzer.repository.VehicleRepository; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class VehicleServiceTest { + + @Mock + private VehicleRepository vehicleRepository; + + @InjectMocks + private VehicleService service; + + @Test + void getAllVehicles_shouldReturnList() { + when(vehicleRepository.findAll()).thenReturn(List.of(new Vehicle(), new Vehicle())); + assertEquals(2, service.getAllVehicles().size()); + } + + @Test + void addVehicle_shouldSetLostDate_whenMissing() { + Vehicle v = new Vehicle(); + v.setNumberPlate("CAB-1234"); + v.setOwnerName("Owner"); + v.setVehicleType("Car"); + + when(vehicleRepository.save(any(Vehicle.class))).thenAnswer(inv -> inv.getArgument(0)); + + Vehicle result = service.addVehicle(v); + + assertNotNull(result.getLostDate()); + } + + @Test + void getVehicleById_shouldReturnVehicle_orNull() { + Vehicle v = new Vehicle(); + v.setId(1L); + when(vehicleRepository.findById(1L)).thenReturn(Optional.of(v)); + when(vehicleRepository.findById(2L)).thenReturn(Optional.empty()); + + assertNotNull(service.getVehicleById(1L)); + assertNull(service.getVehicleById(2L)); + } + + @Test + void deleteVehicle_shouldDelegate() { + service.deleteVehicle(1L); + verify(vehicleRepository).deleteById(1L); + } + + @Test + void updateVehicle_shouldReturnUpdated_whenFound() { + Vehicle existing = new Vehicle(1L, "OLD", "Owner", "Car", "Lost", "2026-03-01"); + Vehicle updates = new Vehicle(null, "NEW", "New Owner", "Van", "Found", "2026-03-02"); + + when(vehicleRepository.findById(1L)).thenReturn(Optional.of(existing)); + when(vehicleRepository.save(any(Vehicle.class))).thenAnswer(inv -> inv.getArgument(0)); + + Vehicle result = service.updateVehicle(1L, updates); + + assertNotNull(result); + assertEquals("NEW", result.getNumberPlate()); + assertEquals("New Owner", result.getOwnerName()); + } + + @Test + void updateVehicle_shouldReturnNull_whenMissing() { + when(vehicleRepository.findById(1L)).thenReturn(Optional.empty()); + assertNull(service.updateVehicle(1L, new Vehicle())); + } + + @Test + void generatePlateRegistryReportPdf_shouldReturnBytes_withoutFilter() { + when(vehicleRepository.findAll()).thenReturn(List.of( + new Vehicle(1L, "A", "O1", "Car", "Lost", "2026-03-01") + )); + + byte[] pdf = service.generatePlateRegistryReportPdf(null, null); + + assertNotNull(pdf); + assertTrue(pdf.length > 0); + } + + @Test + void generatePlateRegistryReportPdf_shouldReturnBytes_withFilter() { + when(vehicleRepository.findAll()).thenReturn(List.of( + new Vehicle(1L, "A", "O1", "Car", "Lost", "2026-03-10"), + new Vehicle(2L, "B", "O2", "Van", "Lost", "2026-04-10") + )); + + byte[] pdf = service.generatePlateRegistryReportPdf("2026-03-01", "2026-03-31"); + + assertNotNull(pdf); + assertTrue(pdf.length > 0); + } +} \ No newline at end of file