From 7d0aa996c6f06be6ec6853f1693df580097c1dbd Mon Sep 17 00:00:00 2001 From: DimaRus05 Date: Thu, 26 Jun 2025 11:15:51 +0300 Subject: [PATCH] some new tests --- .../UserControllerIntegrationTest.java | 349 ++++++++++++++++-- .../service/UserServiceTest.java | 163 +++++++- 2 files changed, 478 insertions(+), 34 deletions(-) diff --git a/src/test/java/com/smartcalendar/controller/UserControllerIntegrationTest.java b/src/test/java/com/smartcalendar/controller/UserControllerIntegrationTest.java index b504e5c..748c9b0 100644 --- a/src/test/java/com/smartcalendar/controller/UserControllerIntegrationTest.java +++ b/src/test/java/com/smartcalendar/controller/UserControllerIntegrationTest.java @@ -19,7 +19,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import org.springframework.http.MediaType; -import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; @@ -67,6 +66,8 @@ private User mockUser(Long id, String username) { return user; } + // --- USERS --- + @Test @WithMockUser void testGetAllUsers() throws Exception { @@ -105,6 +106,48 @@ void testCreateUser() throws Exception { .andExpect(jsonPath("$.username").value("newuser")); } + @Test + @WithMockUser + void testCreateUserWithExistingUsername() throws Exception { + Mockito.when(userService.createUser(any(User.class))) + .thenThrow(new IllegalArgumentException("Username already exists")); + + String json = """ + { + "username": "existinguser", + "email": "newuser@example.com", + "password": "Password123!" + } + """; + mockMvc.perform(post("/api/users") + .contentType(MediaType.APPLICATION_JSON) + .content(json)) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.error").value("Username already exists")); + } + + @Test + @WithMockUser + void testCreateUserWithExistingEmail() throws Exception { + Mockito.when(userService.createUser(any(User.class))) + .thenThrow(new IllegalArgumentException("Email already exists")); + + String json = """ + { + "username": "newuser", + "email": "existing@example.com", + "password": "Password123!" + } + """; + mockMvc.perform(post("/api/users") + .contentType(MediaType.APPLICATION_JSON) + .content(json)) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.error").value("Email already exists")); + } + + // --- EMAIL --- + @Test @WithMockUser void testUpdateEmail() throws Exception { @@ -119,6 +162,19 @@ void testUpdateEmail() throws Exception { .andExpect(jsonPath("$.username").value("testuser")); } + @Test + @WithMockUser + void testUpdateEmail_Forbidden() throws Exception { + User user = mockUser(1L, "testuser"); + Mockito.when(userService.findByUsername(anyString())).thenReturn(Optional.of(user)); + mockMvc.perform(put("/api/users/2/email") + .contentType(MediaType.APPLICATION_JSON) + .content("\"newemail@example.com\"")) + .andExpect(status().isForbidden()); + } + + // --- TASKS --- + @Test @WithMockUser void testGetTasksByUserId() throws Exception { @@ -134,6 +190,65 @@ void testGetTasksByUserId() throws Exception { .andExpect(jsonPath("$[0].id").exists()); } + @Test + @WithMockUser + void testGetTasksByUserId_Forbidden() throws Exception { + User user = mockUser(1L, "testuser"); + Mockito.when(userService.findByUsername(anyString())).thenReturn(Optional.of(user)); + mockMvc.perform(get("/api/users/2/tasks")) + .andExpect(status().isForbidden()); + } + + @Test + @WithMockUser + void testCreateTask() throws Exception { + User user = mockUser(1L, "testuser"); + Task task = new Task(); + task.setId(UUID.randomUUID()); + task.setUser(user); + + Mockito.when(userService.findByUsername(anyString())).thenReturn(Optional.of(user)); + Mockito.when(userService.createTaskWithCustomId(any(Task.class))).thenReturn(task); + + String json = objectMapper.writeValueAsString(task); + mockMvc.perform(post("/api/users/1/tasks") + .contentType(MediaType.APPLICATION_JSON) + .content(json)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").exists()); + } + + @Test + @WithMockUser + void testCreateTask_Forbidden() throws Exception { + User user = mockUser(1L, "testuser"); + Mockito.when(userService.findByUsername(anyString())).thenReturn(Optional.of(user)); + Task task = new Task(); + String json = objectMapper.writeValueAsString(task); + mockMvc.perform(post("/api/users/2/tasks") + .contentType(MediaType.APPLICATION_JSON) + .content(json)) + .andExpect(status().isForbidden()); + } + + @Test + @WithMockUser + void testGetTaskDescription_Forbidden() throws Exception { + User user = mockUser(1L, "testuser"); + Task task = new Task(); + task.setId(UUID.randomUUID()); + User otherUser = mockUser(2L, "otheruser"); + task.setUser(otherUser); + + Mockito.when(userService.getTaskById(any(UUID.class))).thenReturn(task); + Mockito.when(userService.findByUsername(anyString())).thenReturn(Optional.of(user)); + + mockMvc.perform(get("/api/users/tasks/" + task.getId() + "/description")) + .andExpect(status().isForbidden()); + } + + // --- EVENTS --- + @Test @WithMockUser void testGetEventsByUserId() throws Exception { @@ -157,6 +272,15 @@ void testGetEventsByUserId() throws Exception { .andExpect(jsonPath("$[0].organizer.username").value("testuser")); } + @Test + @WithMockUser + void testGetEventsByUserId_Forbidden() throws Exception { + User user = mockUser(1L, "testuser"); + Mockito.when(userService.findByUsername(anyString())).thenReturn(Optional.of(user)); + mockMvc.perform(get("/api/users/2/events")) + .andExpect(status().isForbidden()); + } + @Test @WithMockUser void testCreateEvent() throws Exception { @@ -178,57 +302,159 @@ void testCreateEvent() throws Exception { @Test @WithMockUser - void testCreateTask() throws Exception { + void testCreateEvent_Forbidden() throws Exception { User user = mockUser(1L, "testuser"); - Task task = new Task(); - task.setId(UUID.randomUUID()); - task.setUser(user); + Mockito.when(userService.findByUsername(anyString())).thenReturn(Optional.of(user)); + Event event = new Event(); + String json = objectMapper.writeValueAsString(event); + mockMvc.perform(post("/api/users/2/events") + .contentType(MediaType.APPLICATION_JSON) + .content(json)) + .andExpect(status().isForbidden()); + } + + // --- COLLABORATIVE EVENTS --- + @Test + @WithMockUser + void testInviteUserToEvent_UserAlreadyParticipant() throws Exception { + User user = mockUser(1L, "testuser"); + Event event = new Event(); + event.setId(UUID.randomUUID()); + event.setParticipants(List.of(user)); + Mockito.when(userService.getEventById(event.getId())).thenReturn(event); + Mockito.when(userService.findByLoginOrEmail("testuser")).thenReturn(Optional.of(user)); + + mockMvc.perform(post("/api/users/events/" + event.getId() + "/invite") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"loginOrEmail\":\"testuser\"}")) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.error").value("User is already a participant")); + } + + @Test + @WithMockUser + void testInviteUserToEvent_UserAlreadyInvited() throws Exception { + User user = mockUser(1L, "testuser"); + Event event = new Event(); + event.setId(UUID.randomUUID()); + event.setInvitees(new ArrayList<>(List.of(user.getEmail()))); + Mockito.when(userService.getEventById(event.getId())).thenReturn(event); + Mockito.when(userService.findByLoginOrEmail("testuser")).thenReturn(Optional.of(user)); + + mockMvc.perform(post("/api/users/events/" + event.getId() + "/invite") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"loginOrEmail\":\"testuser\"}")) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.error").value("User is already invited")); + } + + @Test + @WithMockUser + void testInviteUserToEvent_UserNotFound() throws Exception { + Event event = new Event(); + event.setId(UUID.randomUUID()); + Mockito.when(userService.getEventById(event.getId())).thenReturn(event); + Mockito.when(userService.findByLoginOrEmail("nouser")).thenReturn(Optional.empty()); + + mockMvc.perform(post("/api/users/events/" + event.getId() + "/invite") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"loginOrEmail\":\"nouser\"}")) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.error").value("User not found")); + } + + @Test + @WithMockUser + void testAcceptInvite_NoInvite() throws Exception { + User user = mockUser(1L, "testuser"); + Event event = new Event(); + event.setId(UUID.randomUUID()); + event.setInvitees(new ArrayList<>()); Mockito.when(userService.findByUsername(anyString())).thenReturn(Optional.of(user)); - Mockito.when(userService.createTaskWithCustomId(any(Task.class))).thenReturn(task); + Mockito.when(userService.getEventById(event.getId())).thenReturn(event); - String json = objectMapper.writeValueAsString(task); - mockMvc.perform(post("/api/users/1/tasks") + mockMvc.perform(post("/api/users/events/" + event.getId() + "/accept-invite")) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.error").value("No invite found for this user")); + } + + @Test + @WithMockUser + void testRemoveParticipant_NotOrganizer() throws Exception { + User user = mockUser(1L, "testuser"); + User other = mockUser(2L, "other"); + Event event = new Event(); + event.setId(UUID.randomUUID()); + event.setOrganizer(other); + Mockito.when(userService.findByUsername(anyString())).thenReturn(Optional.of(user)); + Mockito.when(userService.getEventById(event.getId())).thenReturn(event); + + mockMvc.perform(post("/api/users/events/" + event.getId() + "/remove-participant") .contentType(MediaType.APPLICATION_JSON) - .content(json)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.id").exists()); + .content("{\"loginOrEmail\":\"other\"}")) + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.error").value("Only organizer can remove participants")); } @Test @WithMockUser - void testGetCurrentUserInfo() throws Exception { + void testRemoveParticipant_OrganizerCannotBeRemoved() throws Exception { User user = mockUser(1L, "testuser"); + Event event = new Event(); + event.setId(UUID.randomUUID()); + event.setOrganizer(user); Mockito.when(userService.findByUsername(anyString())).thenReturn(Optional.of(user)); + Mockito.when(userService.getEventById(event.getId())).thenReturn(event); + Mockito.when(userService.findByLoginOrEmail("testuser")).thenReturn(Optional.of(user)); - mockMvc.perform(get("/api/users/me")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.username").value("testuser")); + mockMvc.perform(post("/api/users/events/" + event.getId() + "/remove-participant") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"loginOrEmail\":\"testuser\"}")) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.error").value("Organizer cannot be removed")); } @Test @WithMockUser - void testGetAllEventsAsDailyTasks() throws Exception { + void testRemoveParticipant_UserNotFound() throws Exception { User user = mockUser(1L, "testuser"); - DailyTaskDto dailyTask = new DailyTaskDto( - UUID.randomUUID(), - "Test Task", - false, - EventType.WORK, - LocalDateTime.now(), - "desc", - LocalTime.of(9, 0), - LocalTime.of(10, 0), - LocalDate.now() - ); + Event event = new Event(); + event.setId(UUID.randomUUID()); + event.setOrganizer(user); Mockito.when(userService.findByUsername(anyString())).thenReturn(Optional.of(user)); - Mockito.when(userService.findAllEventsAsDailyTaskDto(1L)).thenReturn(List.of(dailyTask)); + Mockito.when(userService.getEventById(event.getId())).thenReturn(event); + Mockito.when(userService.findByLoginOrEmail("nouser")).thenReturn(Optional.empty()); - mockMvc.perform(get("/api/users/1/events/dailytasks")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$[0]").exists()); + mockMvc.perform(post("/api/users/events/" + event.getId() + "/remove-participant") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"loginOrEmail\":\"nouser\"}")) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.error").value("User not found")); + } + + @Test + @WithMockUser + void testRemoveParticipant_UserNotParticipant() throws Exception { + User user = mockUser(1L, "testuser"); + User other = mockUser(2L, "other"); + Event event = new Event(); + event.setId(UUID.randomUUID()); + event.setOrganizer(user); + event.setParticipants(new ArrayList<>()); + Mockito.when(userService.findByUsername(anyString())).thenReturn(Optional.of(user)); + Mockito.when(userService.getEventById(event.getId())).thenReturn(event); + Mockito.when(userService.findByLoginOrEmail("other")).thenReturn(Optional.of(other)); + + mockMvc.perform(post("/api/users/events/" + event.getId() + "/remove-participant") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"loginOrEmail\":\"other\"}")) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.error").value("User is not a participant")); } + // --- STATISTICS --- + @Test @WithMockUser void testGetStatistics() throws Exception { @@ -242,6 +468,15 @@ void testGetStatistics() throws Exception { .andExpect(jsonPath("$.totalTime").exists()); } + @Test + @WithMockUser + void testGetStatistics_Forbidden() throws Exception { + User user = mockUser(1L, "testuser"); + Mockito.when(userService.findByUsername(anyString())).thenReturn(Optional.of(user)); + mockMvc.perform(get("/api/users/2/statistics")) + .andExpect(status().isForbidden()); + } + @Test @WithMockUser void testUpdateStatistics() throws Exception { @@ -255,4 +490,56 @@ void testUpdateStatistics() throws Exception { .content(json)) .andExpect(status().isOk()); } + + @Test + @WithMockUser + void testUpdateStatistics_Forbidden() throws Exception { + User user = mockUser(1L, "testuser"); + StatisticsData statisticsData = new StatisticsData(); + Mockito.when(userService.findByUsername(anyString())).thenReturn(Optional.of(user)); + + String json = objectMapper.writeValueAsString(statisticsData); + mockMvc.perform(put("/api/users/2/statistics") + .contentType(MediaType.APPLICATION_JSON) + .content(json)) + .andExpect(status().isForbidden()); + } + + // --- EDGE CASES --- + + @Test + @WithMockUser + void testGetAllEventsAsDailyTasks_Empty() throws Exception { + User user = mockUser(1L, "testuser"); + Mockito.when(userService.findByUsername(anyString())).thenReturn(Optional.of(user)); + Mockito.when(userService.findAllEventsAsDailyTaskDto(1L)).thenReturn(Collections.emptyList()); + + mockMvc.perform(get("/api/users/1/events/dailytasks")) + .andExpect(status().isOk()) + .andExpect(content().json("[]")); + } + + @Test + @WithMockUser + void testGetEventsByUserId_Empty() throws Exception { + User user = mockUser(1L, "testuser"); + Mockito.when(userService.findByUsername(anyString())).thenReturn(Optional.of(user)); + Mockito.when(userService.findEventsByUserId(1L)).thenReturn(Collections.emptyList()); + + mockMvc.perform(get("/api/users/1/events")) + .andExpect(status().isOk()) + .andExpect(content().json("[]")); + } + + @Test + @WithMockUser + void testGetTasksByUserId_Empty() throws Exception { + User user = mockUser(1L, "testuser"); + Mockito.when(userService.findByUsername(anyString())).thenReturn(Optional.of(user)); + Mockito.when(userService.findTasksByUserId(1L)).thenReturn(Collections.emptyList()); + + mockMvc.perform(get("/api/users/1/tasks")) + .andExpect(status().isOk()) + .andExpect(content().json("[]")); + } } \ No newline at end of file diff --git a/src/test/java/com/smartcalendar/service/UserServiceTest.java b/src/test/java/com/smartcalendar/service/UserServiceTest.java index 1cf23b3..a63d89a 100644 --- a/src/test/java/com/smartcalendar/service/UserServiceTest.java +++ b/src/test/java/com/smartcalendar/service/UserServiceTest.java @@ -2,11 +2,14 @@ import com.smartcalendar.model.User; import com.smartcalendar.repository.UserRepository; +import com.smartcalendar.repository.StatisticsRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; import java.util.Optional; @@ -24,6 +27,9 @@ class UserServiceTest { @Mock private PasswordEncoder passwordEncoder; + @Mock + private StatisticsRepository statisticsRepository; + @InjectMocks private UserService userService; @@ -37,7 +43,10 @@ void testCreateUser() { User user = new User(); user.setUsername("testuser"); user.setPassword("password"); + user.setEmail("test@example.com"); + when(userRepository.existsByUsername("testuser")).thenReturn(false); + when(userRepository.existsByEmail("test@example.com")).thenReturn(false); when(passwordEncoder.encode("password")).thenReturn("encodedPassword"); when(userRepository.save(any(User.class))).thenReturn(user); @@ -49,6 +58,31 @@ void testCreateUser() { verify(userRepository).save(user); } + @Test + void testCreateUser_UsernameExists() { + User user = new User(); + user.setUsername("testuser"); + user.setEmail("test@example.com"); + + when(userRepository.existsByUsername("testuser")).thenReturn(true); + + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> userService.createUser(user)); + assertEquals("Username already exists", ex.getMessage()); + } + + @Test + void testCreateUser_EmailExists() { + User user = new User(); + user.setUsername("testuser"); + user.setEmail("test@example.com"); + + when(userRepository.existsByUsername("testuser")).thenReturn(false); + when(userRepository.existsByEmail("test@example.com")).thenReturn(true); + + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> userService.createUser(user)); + assertEquals("Email already exists", ex.getMessage()); + } + @Test void testFindUserById() { User user = new User(); @@ -65,6 +99,12 @@ void testFindUserById() { verify(userRepository).findById(1L); } + @Test + void testFindUserById_NotFound() { + when(userRepository.findById(99L)).thenReturn(Optional.empty()); + assertThrows(RuntimeException.class, () -> userService.findUserById(99L)); + } + @Test void testFindByUsername() { User user = new User(); @@ -77,6 +117,13 @@ void testFindByUsername() { assertEquals("testuser", found.get().getUsername()); } + @Test + void testFindByUsername_NotFound() { + when(userRepository.findByUsername("nouser")).thenReturn(Optional.empty()); + Optional found = userService.findByUsername("nouser"); + assertFalse(found.isPresent()); + } + @Test void testFindByEmail() { User user = new User(); @@ -89,6 +136,13 @@ void testFindByEmail() { assertEquals("test@example.com", found.get().getEmail()); } + @Test + void testFindByEmail_NotFound() { + when(userRepository.findByEmail("no@mail.com")).thenReturn(Optional.empty()); + Optional found = userService.findByEmail("no@mail.com"); + assertFalse(found.isPresent()); + } + @Test void testExistsByUsername() { when(userRepository.existsByUsername("testuser")).thenReturn(true); @@ -114,6 +168,12 @@ void testUpdateEmail() { assertEquals("new@example.com", updated.getEmail()); } + @Test + void testUpdateEmail_NotFound() { + when(userRepository.findById(2L)).thenReturn(Optional.empty()); + assertThrows(RuntimeException.class, () -> userService.updateEmail(2L, "new@example.com")); + } + @Test void testFindAllUsers() { User user = new User(); @@ -133,8 +193,105 @@ void testDeleteUser() { } @Test - void testFindUserById_NotFound() { - when(userRepository.findById(99L)).thenReturn(Optional.empty()); - assertThrows(RuntimeException.class, () -> userService.findUserById(99L)); + void testDeleteAllUsersAndStatistics() { + doNothing().when(statisticsRepository).deleteAll(); + doNothing().when(userRepository).deleteAll(); + userService.deleteAllUsersAndStatistics(); + verify(statisticsRepository).deleteAll(); + verify(userRepository).deleteAll(); + } + + @Test + void testUserDetailsService() { + User user = new User(); + user.setUsername("testuser"); + user.setPassword("pass"); + when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(user)); + UserDetails details = userService.userDetailsService().loadUserByUsername("testuser"); + assertEquals("testuser", details.getUsername()); + } + + @Test + void testLoadUserByUsername_NotFound() { + when(userRepository.findByUsername("nouser")).thenReturn(Optional.empty()); + assertThrows(UsernameNotFoundException.class, () -> userService.loadUserByUsername("nouser")); + } + + @Test + void testLoadUserByEmail() { + User user = new User(); + user.setUsername("testuser"); + user.setPassword("pass"); + user.setEmail("test@example.com"); + when(userRepository.findByEmail("test@example.com")).thenReturn(Optional.of(user)); + UserDetails details = userService.loadUserByEmail("test@example.com"); + assertEquals("testuser", details.getUsername()); + } + + @Test + void testLoadUserByEmail_NotFound() { + when(userRepository.findByEmail("no@mail.com")).thenReturn(Optional.empty()); + assertThrows(UsernameNotFoundException.class, () -> userService.loadUserByEmail("no@mail.com")); + } + + @Test + void testChangeCredentials_Success() { + User user = new User(); + user.setUsername("olduser"); + user.setPassword("oldpass"); + when(userRepository.findByUsername("olduser")).thenReturn(Optional.of(user)); + when(passwordEncoder.matches("oldpass", "oldpass")).thenReturn(true); + when(userRepository.save(any(User.class))).thenReturn(user); + + boolean result = userService.changeCredentials("olduser", "oldpass", "newuser", "newpass"); + assertTrue(result); + assertEquals("newuser", user.getUsername()); + } + + @Test + void testChangeCredentials_Fail_WrongPassword() { + User user = new User(); + user.setUsername("olduser"); + user.setPassword("oldpass"); + when(userRepository.findByUsername("olduser")).thenReturn(Optional.of(user)); + when(passwordEncoder.matches("wrong", "oldpass")).thenReturn(false); + + boolean result = userService.changeCredentials("olduser", "wrong", "newuser", "newpass"); + assertFalse(result); + } + + @Test + void testChangeCredentials_UserNotFound() { + when(userRepository.findByUsername("nouser")).thenReturn(Optional.empty()); + assertThrows(RuntimeException.class, () -> userService.changeCredentials("nouser", "pass", "new", "new")); + } + + @Test + void testFindByLoginOrEmail_ByUsername() { + User user = new User(); + user.setUsername("testuser"); + when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(user)); + Optional found = userService.findByLoginOrEmail("testuser"); + assertTrue(found.isPresent()); + assertEquals("testuser", found.get().getUsername()); + } + + @Test + void testFindByLoginOrEmail_ByEmail() { + User user = new User(); + user.setEmail("test@example.com"); + when(userRepository.findByUsername("test@example.com")).thenReturn(Optional.empty()); + when(userRepository.findByEmail("test@example.com")).thenReturn(Optional.of(user)); + Optional found = userService.findByLoginOrEmail("test@example.com"); + assertTrue(found.isPresent()); + assertEquals("test@example.com", found.get().getEmail()); + } + + @Test + void testFindByLoginOrEmail_NotFound() { + when(userRepository.findByUsername("nouser")).thenReturn(Optional.empty()); + when(userRepository.findByEmail("nouser")).thenReturn(Optional.empty()); + Optional found = userService.findByLoginOrEmail("nouser"); + assertFalse(found.isPresent()); } } \ No newline at end of file