From 02fb951d40f55a1735ea24beddd46c5d278f76bc Mon Sep 17 00:00:00 2001 From: Martin Karlsson Date: Thu, 16 Apr 2026 10:04:07 +0200 Subject: [PATCH 1/7] Feature: - First implementation of tests --- .../TestcontainersConfiguration.java | 2 +- .../controller/UserViewControllerTest.java | 307 +++++++++++++++++ .../user/mapper/UserMapperTest.java | 156 +++++++++ .../user/service/UserServiceTest.java | 325 ++++++++++++++++++ 4 files changed, 789 insertions(+), 1 deletion(-) create mode 100644 src/test/java/org/example/visacasemanagementsystem/user/controller/UserViewControllerTest.java create mode 100644 src/test/java/org/example/visacasemanagementsystem/user/mapper/UserMapperTest.java create mode 100644 src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceTest.java diff --git a/src/test/java/org/example/visacasemanagementsystem/TestcontainersConfiguration.java b/src/test/java/org/example/visacasemanagementsystem/TestcontainersConfiguration.java index 0ccc615..9f601bb 100644 --- a/src/test/java/org/example/visacasemanagementsystem/TestcontainersConfiguration.java +++ b/src/test/java/org/example/visacasemanagementsystem/TestcontainersConfiguration.java @@ -7,7 +7,7 @@ import org.testcontainers.utility.DockerImageName; @TestConfiguration(proxyBeanMethods = false) -class TestcontainersConfiguration { +public class TestcontainersConfiguration { @Bean @ServiceConnection diff --git a/src/test/java/org/example/visacasemanagementsystem/user/controller/UserViewControllerTest.java b/src/test/java/org/example/visacasemanagementsystem/user/controller/UserViewControllerTest.java new file mode 100644 index 0000000..4e20205 --- /dev/null +++ b/src/test/java/org/example/visacasemanagementsystem/user/controller/UserViewControllerTest.java @@ -0,0 +1,307 @@ +package org.example.visacasemanagementsystem.user.controller; + +import org.example.visacasemanagementsystem.TestcontainersConfiguration; +import org.example.visacasemanagementsystem.user.UserAuthorization; +import org.example.visacasemanagementsystem.user.entity.User; +import org.example.visacasemanagementsystem.user.repository.UserRepository; +import org.example.visacasemanagementsystem.user.security.SecurityUser; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Import; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +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.*; + +@Import(TestcontainersConfiguration.class) +@SpringBootTest +@AutoConfigureMockMvc +@Transactional +class UserViewControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private UserRepository userRepository; + + private User savedUser; + private User savedAdmin; + private User savedSysAdmin; + + @BeforeEach + void setUp() { + User user = new User(); + user.setFullName("Test User"); + user.setEmail("user@controller.test"); + user.setPassword("password123"); + user.setUserAuthorization(UserAuthorization.USER); + savedUser = userRepository.saveAndFlush(user); + + User admin = new User(); + admin.setFullName("Test Admin"); + admin.setEmail("admin@controller.test"); + admin.setPassword("password123"); + admin.setUserAuthorization(UserAuthorization.ADMIN); + savedAdmin = userRepository.saveAndFlush(admin); + + User sysadmin = new User(); + sysadmin.setFullName("Test SysAdmin"); + sysadmin.setEmail("sysadmin@controller.test"); + sysadmin.setPassword("password123"); + sysadmin.setUserAuthorization(UserAuthorization.SYSADMIN); + savedSysAdmin = userRepository.saveAndFlush(sysadmin); + } + + /** Build a fully populated {@link Authentication} from a persisted {@link User}. */ + private Authentication authFor(User user) { + SecurityUser principal = new SecurityUser(user); + return new UsernamePasswordAuthenticationToken(principal, null, principal.getAuthorities()); + } + + // ── GET /user/signup ────────────────────────────────────────────────────── + + @Test + void userSignupForm_ReturnsSignupView() throws Exception { + mockMvc.perform(get("/user/signup")) + .andExpect(status().isOk()) + .andExpect(view().name("user/signup")); + } + + // ── POST /user/signup ───────────────────────────────────────────────────── + + @Test + void createUser_WithValidData_RedirectsToLogin() throws Exception { + mockMvc.perform(post("/user/signup") + .param("fullName", "New Applicant") + .param("email", "newapplicant@controller.test") + .param("password", "securePass1") + .with(csrf())) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("/user/login")); + } + + @Test + void createUser_WithDuplicateEmail_ReturnsSignupViewWithError() throws Exception { + mockMvc.perform(post("/user/signup") + .param("fullName", "Duplicate User") + .param("email", "user@controller.test") // already seeded + .param("password", "password123") + .with(csrf())) + .andExpect(status().isOk()) + .andExpect(view().name("user/signup")) + .andExpect(model().attributeExists("error")); + } + + @Test + void createUser_WithShortPassword_ReturnsSignupViewWithError() throws Exception { + mockMvc.perform(post("/user/signup") + .param("fullName", "Short Pass") + .param("email", "shortpass@controller.test") + .param("password", "abc") + .with(csrf())) + .andExpect(status().isOk()) + .andExpect(view().name("user/signup")) + .andExpect(model().attributeExists("error")); + } + + // ── GET /user/login ─────────────────────────────────────────────────────── + + @Test + void userLoginForm_ReturnsLoginView() throws Exception { + mockMvc.perform(get("/user/login")) + .andExpect(status().isOk()) + .andExpect(view().name("user/login")); + } + + // ── GET /profile/view/{userId} ──────────────────────────────────────────── + + @Test + void viewProfile_AsOwnUser_ReturnsProfileViewWithCanEditTrue() throws Exception { + mockMvc.perform(get("/profile/view/" + savedUser.getId()) + .with(authentication(authFor(savedUser)))) + .andExpect(status().isOk()) + .andExpect(view().name("profile/view")) + .andExpect(model().attributeExists("user")) + .andExpect(model().attribute("canEdit", true)); + } + + @Test + void viewProfile_AsSysAdminViewingOtherUser_ReturnsProfileViewWithCanEditTrue() throws Exception { + mockMvc.perform(get("/profile/view/" + savedUser.getId()) + .with(authentication(authFor(savedSysAdmin)))) + .andExpect(status().isOk()) + .andExpect(view().name("profile/view")) + .andExpect(model().attribute("canEdit", true)); + } + + @Test + void viewProfile_AsAdminViewingOtherUser_ReturnsForbidden() throws Exception { + mockMvc.perform(get("/profile/view/" + savedSysAdmin.getId()) + .with(authentication(authFor(savedAdmin)))) + .andExpect(status().isForbidden()); + } + + @Test + void viewProfile_AsUserViewingOtherProfile_ReturnsForbidden() throws Exception { + mockMvc.perform(get("/profile/view/" + savedAdmin.getId()) + .with(authentication(authFor(savedUser)))) + .andExpect(status().isForbidden()); + } + + // ── GET /profile/edit/{userId} ──────────────────────────────────────────── + + @Test + void showProfileEditForm_AsOwnUser_ReturnsEditViewWithUserModel() throws Exception { + mockMvc.perform(get("/profile/edit/" + savedUser.getId()) + .with(authentication(authFor(savedUser)))) + .andExpect(status().isOk()) + .andExpect(view().name("profile/edit")) + .andExpect(model().attributeExists("user")); + } + + @Test + void showProfileEditForm_AsSysAdmin_ReturnsEditView() throws Exception { + mockMvc.perform(get("/profile/edit/" + savedUser.getId()) + .with(authentication(authFor(savedSysAdmin)))) + .andExpect(status().isOk()) + .andExpect(view().name("profile/edit")); + } + + @Test + void showProfileEditForm_AsUnauthorizedUser_ReturnsForbidden() throws Exception { + mockMvc.perform(get("/profile/edit/" + savedSysAdmin.getId()) + .with(authentication(authFor(savedUser)))) + .andExpect(status().isForbidden()); + } + + // ── POST /profile/edit/{userId} ─────────────────────────────────────────── + + @Test + void updateProfile_WithValidData_RedirectsToProfileView() throws Exception { + mockMvc.perform(post("/profile/edit/" + savedUser.getId()) + .param("fullName", "Updated Name") + .param("email", "updatedname@controller.test") + .with(authentication(authFor(savedUser))) + .with(csrf())) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("/profile/view/" + savedUser.getId())); + } + + @Test + void updateProfile_WithDuplicateEmail_ReturnsEditViewWithError() throws Exception { + mockMvc.perform(post("/profile/edit/" + savedUser.getId()) + .param("fullName", "Test User") + .param("email", "admin@controller.test") // already owned by savedAdmin + .with(authentication(authFor(savedUser))) + .with(csrf())) + .andExpect(status().isOk()) + .andExpect(view().name("profile/edit")) + .andExpect(model().attributeExists("error")); + } + + @Test + void updateProfile_AsUnauthorizedUser_ReturnsForbidden() throws Exception { + mockMvc.perform(post("/profile/edit/" + savedAdmin.getId()) + .param("fullName", "Hacked Name") + .param("email", "hacked@controller.test") + .with(authentication(authFor(savedUser))) + .with(csrf())) + .andExpect(status().isForbidden()); + } + + // ── GET /user/list ──────────────────────────────────────────────────────── + + @Test + void userListView_AsSysAdmin_ReturnsListViewWithUsers() throws Exception { + mockMvc.perform(get("/user/list") + .with(authentication(authFor(savedSysAdmin)))) + .andExpect(status().isOk()) + .andExpect(view().name("user/list")) + .andExpect(model().attributeExists("users")) + .andExpect(model().attributeExists("name")); + } + + @Test + void userListView_AsNonSysAdmin_ReturnsForbidden() throws Exception { + mockMvc.perform(get("/user/list") + .with(authentication(authFor(savedUser)))) + .andExpect(status().isForbidden()); + } + + // ── GET /dashboard/applicant ────────────────────────────────────────────── + + @Test + void applicantDashboard_AsUser_ReturnsDashboardViewWithVisas() throws Exception { + mockMvc.perform(get("/dashboard/applicant") + .with(authentication(authFor(savedUser)))) + .andExpect(status().isOk()) + .andExpect(view().name("dashboard/applicant")) + .andExpect(model().attributeExists("visas")) + .andExpect(model().attributeExists("name")); + } + + // ── GET /dashboard/admin ────────────────────────────────────────────────── + + @Test + void adminDashboard_AsAdmin_ReturnsDashboardViewWithCases() throws Exception { + mockMvc.perform(get("/dashboard/admin") + .with(authentication(authFor(savedAdmin)))) + .andExpect(status().isOk()) + .andExpect(view().name("dashboard/admin")) + .andExpect(model().attributeExists("assignedCases")) + .andExpect(model().attributeExists("unassignedCases")) + .andExpect(model().attributeExists("name")); + } + + @Test + void adminDashboard_AsSysAdmin_ReturnsDashboardView() throws Exception { + mockMvc.perform(get("/dashboard/admin") + .with(authentication(authFor(savedSysAdmin)))) + .andExpect(status().isOk()) + .andExpect(view().name("dashboard/admin")); + } + + @Test + void adminDashboard_AsRegularUser_ReturnsForbidden() throws Exception { + mockMvc.perform(get("/dashboard/admin") + .with(authentication(authFor(savedUser)))) + .andExpect(status().isForbidden()); + } + + // ── GET /dashboard/sysadmin ─────────────────────────────────────────────── + + @Test + void sysAdminDashboard_AsSysAdmin_ReturnsDashboardViewWithUsersAndAuditLogs() throws Exception { + mockMvc.perform(get("/dashboard/sysadmin") + .with(authentication(authFor(savedSysAdmin)))) + .andExpect(status().isOk()) + .andExpect(view().name("dashboard/sysadmin")) + .andExpect(model().attributeExists("users")) + .andExpect(model().attributeExists("auditLogs")) + .andExpect(model().attributeExists("name")); + } + + @Test + void sysAdminDashboard_AsAdmin_ReturnsForbidden() throws Exception { + mockMvc.perform(get("/dashboard/sysadmin") + .with(authentication(authFor(savedAdmin)))) + .andExpect(status().isForbidden()); + } + + @Test + void sysAdminDashboard_AsRegularUser_ReturnsForbidden() throws Exception { + mockMvc.perform(get("/dashboard/sysadmin") + .with(authentication(authFor(savedUser)))) + .andExpect(status().isForbidden()); + } +} diff --git a/src/test/java/org/example/visacasemanagementsystem/user/mapper/UserMapperTest.java b/src/test/java/org/example/visacasemanagementsystem/user/mapper/UserMapperTest.java new file mode 100644 index 0000000..1e49359 --- /dev/null +++ b/src/test/java/org/example/visacasemanagementsystem/user/mapper/UserMapperTest.java @@ -0,0 +1,156 @@ +package org.example.visacasemanagementsystem.user.mapper; + +import org.example.visacasemanagementsystem.user.UserAuthorization; +import org.example.visacasemanagementsystem.user.dto.CreateUserDTO; +import org.example.visacasemanagementsystem.user.dto.UpdateUserDTO; +import org.example.visacasemanagementsystem.user.dto.UserDTO; +import org.example.visacasemanagementsystem.user.entity.User; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class UserMapperTest { + + private UserMapper userMapper; + + @BeforeEach + void setUp() { + userMapper = new UserMapper(); + } + + // ── toDTO ───────────────────────────────────────────────────────────────── + + @Test + void toDTO_WithValidUser_ReturnsMappedUserDTO() { + User user = new User(); + user.setFullName("Alice Wonderland"); + user.setEmail("alice@example.com"); + user.setPassword("password123"); + user.setUserAuthorization(UserAuthorization.USER); + + UserDTO dto = userMapper.toDTO(user); + + assertNotNull(dto); + assertEquals("Alice Wonderland", dto.fullName()); + assertEquals("alice@example.com", dto.email()); + assertEquals(UserAuthorization.USER, dto.userAuthorization()); + } + + @Test + void toDTO_WithAllAuthorizationLevels_MapsCorrectly() { + for (UserAuthorization auth : UserAuthorization.values()) { + User user = new User(); + user.setFullName("Test User"); + user.setEmail("test@example.com"); + user.setPassword("password123"); + user.setUserAuthorization(auth); + + UserDTO dto = userMapper.toDTO(user); + + assertNotNull(dto); + assertEquals(auth, dto.userAuthorization()); + } + } + + @Test + void toDTO_WithNullUser_ReturnsNull() { + assertNull(userMapper.toDTO(null)); + } + + // ── toEntity ────────────────────────────────────────────────────────────── + + @Test + void toEntity_WithValidCreateUserDTO_ReturnsMappedUser() { + CreateUserDTO dto = new CreateUserDTO( + "Bob Builder", + "bob@example.com", + "password123", + UserAuthorization.ADMIN + ); + + User user = userMapper.toEntity(dto); + + assertNotNull(user); + assertEquals("Bob Builder", user.getFullName()); + assertEquals("bob@example.com", user.getEmail()); + assertEquals(UserAuthorization.ADMIN, user.getUserAuthorization()); + } + + @Test + void toEntity_DoesNotSetPassword() { + // The mapper intentionally leaves password unset; UserService sets it separately + CreateUserDTO dto = new CreateUserDTO( + "No Pass", + "nopass@example.com", + "supersecret", + UserAuthorization.USER + ); + + User user = userMapper.toEntity(dto); + + assertNotNull(user); + assertNull(user.getPassword()); + } + + @Test + void toEntity_WithNullCreateUserDTO_ReturnsNull() { + assertNull(userMapper.toEntity(null)); + } + + // ── updateEntityFromDTO ─────────────────────────────────────────────────── + + @Test + void updateEntityFromDTO_WithValidInputs_UpdatesFullNameAndEmail() { + User user = new User(); + user.setFullName("Old Name"); + user.setEmail("old@example.com"); + user.setPassword("password123"); + user.setUserAuthorization(UserAuthorization.USER); + + UpdateUserDTO dto = new UpdateUserDTO(1L, "New Name", "new@example.com"); + userMapper.updateEntityFromDTO(dto, user); + + assertEquals("New Name", user.getFullName()); + assertEquals("new@example.com", user.getEmail()); + } + + @Test + void updateEntityFromDTO_DoesNotModifyAuthorizationOrPassword() { + User user = new User(); + user.setFullName("Original"); + user.setEmail("original@example.com"); + user.setPassword("secret123"); + user.setUserAuthorization(UserAuthorization.SYSADMIN); + + UpdateUserDTO dto = new UpdateUserDTO(1L, "Changed Name", "changed@example.com"); + userMapper.updateEntityFromDTO(dto, user); + + // Authorization and password must remain untouched + assertEquals(UserAuthorization.SYSADMIN, user.getUserAuthorization()); + assertEquals("secret123", user.getPassword()); + } + + @Test + void updateEntityFromDTO_WithNullDTO_DoesNotUpdateEntity() { + User user = new User(); + user.setFullName("Original Name"); + user.setEmail("original@example.com"); + + userMapper.updateEntityFromDTO(null, user); + + assertEquals("Original Name", user.getFullName()); + assertEquals("original@example.com", user.getEmail()); + } + + @Test + void updateEntityFromDTO_WithNullUser_DoesNotThrow() { + UpdateUserDTO dto = new UpdateUserDTO(1L, "Some Name", "some@example.com"); + assertDoesNotThrow(() -> userMapper.updateEntityFromDTO(dto, null)); + } + + @Test + void updateEntityFromDTO_WithBothNull_DoesNotThrow() { + assertDoesNotThrow(() -> userMapper.updateEntityFromDTO(null, null)); + } +} diff --git a/src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceTest.java b/src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceTest.java new file mode 100644 index 0000000..6af1da6 --- /dev/null +++ b/src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceTest.java @@ -0,0 +1,325 @@ +package org.example.visacasemanagementsystem.user.service; + +import jakarta.persistence.EntityNotFoundException; +import org.example.visacasemanagementsystem.TestcontainersConfiguration; +import org.example.visacasemanagementsystem.exception.UnauthorizedException; +import org.example.visacasemanagementsystem.user.UserAuthorization; +import org.example.visacasemanagementsystem.user.dto.CreateUserDTO; +import org.example.visacasemanagementsystem.user.dto.UpdateUserDTO; +import org.example.visacasemanagementsystem.user.dto.UserDTO; +import org.example.visacasemanagementsystem.user.entity.User; +import org.example.visacasemanagementsystem.user.repository.UserRepository; +import org.example.visacasemanagementsystem.user.security.SecurityUser; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Import; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; + +@Import(TestcontainersConfiguration.class) +@SpringBootTest +@Transactional +class UserServiceTest { + + @Autowired + private UserService userService; + + @Autowired + private UserRepository userRepository; + + private User savedUser; + private User savedAdmin; + private User savedSysAdmin; + + @BeforeEach + void setUp() { + User user = new User(); + user.setFullName("Test User"); + user.setEmail("user@service.test"); + user.setPassword("password123"); + user.setUserAuthorization(UserAuthorization.USER); + savedUser = userRepository.save(user); + + User admin = new User(); + admin.setFullName("Test Admin"); + admin.setEmail("admin@service.test"); + admin.setPassword("password123"); + admin.setUserAuthorization(UserAuthorization.ADMIN); + savedAdmin = userRepository.save(admin); + + User sysadmin = new User(); + sysadmin.setFullName("Test SysAdmin"); + sysadmin.setEmail("sysadmin@service.test"); + sysadmin.setPassword("password123"); + sysadmin.setUserAuthorization(UserAuthorization.SYSADMIN); + savedSysAdmin = userRepository.save(sysadmin); + } + + // ── findAll ─────────────────────────────────────────────────────────────── + + @Test + void findAll_ReturnsListContainingAllSeededUsers() { + List result = userService.findAll(); + + assertNotNull(result); + assertTrue(result.size() >= 3); + assertTrue(result.stream().anyMatch(u -> u.email().equals("user@service.test"))); + assertTrue(result.stream().anyMatch(u -> u.email().equals("admin@service.test"))); + assertTrue(result.stream().anyMatch(u -> u.email().equals("sysadmin@service.test"))); + } + + // ── findById ────────────────────────────────────────────────────────────── + + @Test + void findById_WithExistingId_ReturnsMatchingUser() { + Optional result = userService.findById(savedUser.getId()); + + assertTrue(result.isPresent()); + assertEquals("Test User", result.get().fullName()); + assertEquals("user@service.test", result.get().email()); + assertEquals(UserAuthorization.USER, result.get().userAuthorization()); + } + + @Test + void findById_WithNonExistingId_ReturnsEmpty() { + Optional result = userService.findById(999999L); + assertFalse(result.isPresent()); + } + + // ── findByEmail ─────────────────────────────────────────────────────────── + + @Test + void findByEmail_WithExistingEmail_ReturnsMatchingUser() { + Optional result = userService.findByEmail("admin@service.test"); + + assertTrue(result.isPresent()); + assertEquals("Test Admin", result.get().fullName()); + assertEquals(UserAuthorization.ADMIN, result.get().userAuthorization()); + } + + @Test + void findByEmail_WithNonExistingEmail_ReturnsEmpty() { + Optional result = userService.findByEmail("nobody@nowhere.test"); + assertFalse(result.isPresent()); + } + + // ── createUser ──────────────────────────────────────────────────────────── + + @Test + void createUser_WithValidData_PersistsAndReturnsNewUser() { + CreateUserDTO dto = new CreateUserDTO( + "New Applicant", + "newapplicant@service.test", + "securePass1", + UserAuthorization.USER + ); + + UserDTO result = userService.createUser(dto); + + assertNotNull(result); + assertNotNull(result.id()); + assertEquals("New Applicant", result.fullName()); + assertEquals("newapplicant@service.test", result.email()); + } + + @Test + void createUser_WithDuplicateEmail_ThrowsIllegalArgumentException() { + CreateUserDTO dto = new CreateUserDTO( + "Duplicate", + "user@service.test", + "password123", + UserAuthorization.USER + ); + + assertThrows(IllegalArgumentException.class, () -> userService.createUser(dto)); + } + + @Test + void createUser_WithShortPassword_ThrowsIllegalArgumentException() { + CreateUserDTO dto = new CreateUserDTO( + "Short Pass", + "shortpass@service.test", + "abc", + UserAuthorization.USER + ); + + IllegalArgumentException ex = assertThrows( + IllegalArgumentException.class, + () -> userService.createUser(dto) + ); + assertTrue(ex.getMessage().contains("8 characters")); + } + + // ── updateUser ──────────────────────────────────────────────────────────── + + @Test + void updateUser_WithValidData_UpdatesAndReturnsUser() { + UpdateUserDTO dto = new UpdateUserDTO( + savedUser.getId(), + "Updated Name", + "updated@service.test" + ); + + UserDTO result = userService.updateUser(dto); + + assertNotNull(result); + assertEquals("Updated Name", result.fullName()); + assertEquals("updated@service.test", result.email()); + } + + @Test + void updateUser_WithNonExistingId_ThrowsEntityNotFoundException() { + UpdateUserDTO dto = new UpdateUserDTO(999999L, "Name", "missing@service.test"); + + assertThrows(EntityNotFoundException.class, () -> userService.updateUser(dto)); + } + + @Test + void updateUser_WithEmailAlreadyOwnedByAnotherUser_ThrowsIllegalArgumentException() { + // Try to update savedUser's email to admin's existing email + UpdateUserDTO dto = new UpdateUserDTO( + savedUser.getId(), + "Test User", + "admin@service.test" + ); + + assertThrows(IllegalArgumentException.class, () -> userService.updateUser(dto)); + } + + // ── updateUserAuthorization ─────────────────────────────────────────────── + + @Test + void updateUserAuthorization_BySysAdmin_ChangesAuthorizationLevel() { + UserDTO result = userService.updateUserAuthorization( + savedUser.getId(), + UserAuthorization.ADMIN, + savedSysAdmin.getId() + ); + + assertEquals(UserAuthorization.ADMIN, result.userAuthorization()); + } + + @Test + void updateUserAuthorization_ByNonSysAdmin_ThrowsUnauthorizedException() { + assertThrows(UnauthorizedException.class, () -> + userService.updateUserAuthorization( + savedAdmin.getId(), + UserAuthorization.SYSADMIN, + savedUser.getId() + ) + ); + } + + @Test + void updateUserAuthorization_WithNonExistingTargetUser_ThrowsEntityNotFoundException() { + assertThrows(EntityNotFoundException.class, () -> + userService.updateUserAuthorization( + 999999L, + UserAuthorization.ADMIN, + savedSysAdmin.getId() + ) + ); + } + + // ── deleteUser ──────────────────────────────────────────────────────────── + + @Test + void deleteUser_BySysAdmin_RemovesUserFromDatabase() { + Long userId = savedUser.getId(); + + userService.deleteUser(userId, savedSysAdmin.getId()); + + assertFalse(userRepository.findById(userId).isPresent()); + } + + @Test + void deleteUser_ByNonSysAdmin_ThrowsUnauthorizedException() { + assertThrows(UnauthorizedException.class, () -> + userService.deleteUser(savedAdmin.getId(), savedUser.getId()) + ); + } + + @Test + void deleteUser_WithNonExistingUser_ThrowsEntityNotFoundException() { + assertThrows(EntityNotFoundException.class, () -> + userService.deleteUser(999999L, savedSysAdmin.getId()) + ); + } + + // ── validateProfileAccess ───────────────────────────────────────────────── + + @Test + void validateProfileAccess_OwnProfile_DoesNotThrow() { + SecurityUser principal = new SecurityUser(savedUser); + + assertDoesNotThrow(() -> userService.validateProfileAccess(principal, savedUser.getId())); + } + + @Test + void validateProfileAccess_SysAdminViewingOtherProfile_DoesNotThrow() { + SecurityUser principal = new SecurityUser(savedSysAdmin); + + assertDoesNotThrow(() -> userService.validateProfileAccess(principal, savedUser.getId())); + } + + @Test + void validateProfileAccess_UserViewingOtherProfile_ThrowsUnauthorizedException() { + SecurityUser principal = new SecurityUser(savedUser); + + assertThrows(UnauthorizedException.class, () -> + userService.validateProfileAccess(principal, savedAdmin.getId()) + ); + } + + // ── validateSysAdmin (SecurityUser overload) ────────────────────────────── + + @Test + void validateSysAdmin_WithSysAdminPrincipal_DoesNotThrow() { + SecurityUser principal = new SecurityUser(savedSysAdmin); + + assertDoesNotThrow(() -> userService.validateSysAdmin(principal)); + } + + @Test + void validateSysAdmin_WithAdminPrincipal_ThrowsUnauthorizedException() { + SecurityUser principal = new SecurityUser(savedAdmin); + + assertThrows(UnauthorizedException.class, () -> userService.validateSysAdmin(principal)); + } + + @Test + void validateSysAdmin_WithUserPrincipal_ThrowsUnauthorizedException() { + SecurityUser principal = new SecurityUser(savedUser); + + assertThrows(UnauthorizedException.class, () -> userService.validateSysAdmin(principal)); + } + + // ── validateAdmin ───────────────────────────────────────────────────────── + + @Test + void validateAdmin_WithAdminPrincipal_DoesNotThrow() { + SecurityUser principal = new SecurityUser(savedAdmin); + + assertDoesNotThrow(() -> userService.validateAdmin(principal)); + } + + @Test + void validateAdmin_WithSysAdminPrincipal_DoesNotThrow() { + SecurityUser principal = new SecurityUser(savedSysAdmin); + + assertDoesNotThrow(() -> userService.validateAdmin(principal)); + } + + @Test + void validateAdmin_WithUserPrincipal_ThrowsUnauthorizedException() { + SecurityUser principal = new SecurityUser(savedUser); + + assertThrows(UnauthorizedException.class, () -> userService.validateAdmin(principal)); + } +} From c7705a2762d04c0c8f833d9ab7ff4b0fc9b599d6 Mon Sep 17 00:00:00 2001 From: Martin Karlsson Date: Thu, 16 Apr 2026 18:03:08 +0200 Subject: [PATCH 2/7] Feature: - Added UserServiceIntegrationTest for integration tests. - Updated unit tests for Mapper, Service and ViewController to match those in Visa tests. - Added display name annotations to all user tests. --- .../controller/UserViewControllerTest.java | 359 ++++++++----- .../user/mapper/UserMapperTest.java | 123 +++-- .../service/UserServiceIntegrationTest.java | 241 +++++++++ .../user/service/UserServiceTest.java | 493 ++++++++++-------- 4 files changed, 822 insertions(+), 394 deletions(-) create mode 100644 src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceIntegrationTest.java diff --git a/src/test/java/org/example/visacasemanagementsystem/user/controller/UserViewControllerTest.java b/src/test/java/org/example/visacasemanagementsystem/user/controller/UserViewControllerTest.java index 4e20205..f198305 100644 --- a/src/test/java/org/example/visacasemanagementsystem/user/controller/UserViewControllerTest.java +++ b/src/test/java/org/example/visacasemanagementsystem/user/controller/UserViewControllerTest.java @@ -1,77 +1,56 @@ package org.example.visacasemanagementsystem.user.controller; -import org.example.visacasemanagementsystem.TestcontainersConfiguration; +import org.example.visacasemanagementsystem.audit.service.AuditService; +import org.example.visacasemanagementsystem.exception.UnauthorizedException; import org.example.visacasemanagementsystem.user.UserAuthorization; +import org.example.visacasemanagementsystem.user.dto.UserDTO; import org.example.visacasemanagementsystem.user.entity.User; -import org.example.visacasemanagementsystem.user.repository.UserRepository; import org.example.visacasemanagementsystem.user.security.SecurityUser; -import org.junit.jupiter.api.BeforeEach; +import org.example.visacasemanagementsystem.user.service.UserService; +import org.example.visacasemanagementsystem.visa.service.VisaService; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Import; +import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; -import org.springframework.transaction.annotation.Transactional; +import java.util.List; +import java.util.Optional; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; 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.*; -@Import(TestcontainersConfiguration.class) -@SpringBootTest -@AutoConfigureMockMvc -@Transactional +@WebMvcTest(UserViewController.class) +@DisplayName("UserViewController web-layer tests") class UserViewControllerTest { @Autowired - private MockMvc mockMvc; - - @Autowired - private UserRepository userRepository; + MockMvc mockMvc; - private User savedUser; - private User savedAdmin; - private User savedSysAdmin; - - @BeforeEach - void setUp() { - User user = new User(); - user.setFullName("Test User"); - user.setEmail("user@controller.test"); - user.setPassword("password123"); - user.setUserAuthorization(UserAuthorization.USER); - savedUser = userRepository.saveAndFlush(user); - - User admin = new User(); - admin.setFullName("Test Admin"); - admin.setEmail("admin@controller.test"); - admin.setPassword("password123"); - admin.setUserAuthorization(UserAuthorization.ADMIN); - savedAdmin = userRepository.saveAndFlush(admin); - - User sysadmin = new User(); - sysadmin.setFullName("Test SysAdmin"); - sysadmin.setEmail("sysadmin@controller.test"); - sysadmin.setPassword("password123"); - sysadmin.setUserAuthorization(UserAuthorization.SYSADMIN); - savedSysAdmin = userRepository.saveAndFlush(sysadmin); - } - - /** Build a fully populated {@link Authentication} from a persisted {@link User}. */ - private Authentication authFor(User user) { - SecurityUser principal = new SecurityUser(user); - return new UsernamePasswordAuthenticationToken(principal, null, principal.getAuthorities()); - } + @MockitoBean + private UserService userService; + @MockitoBean + private VisaService visaService; + @MockitoBean + private AuditService auditService; // ── GET /user/signup ────────────────────────────────────────────────────── @Test - void userSignupForm_ReturnsSignupView() throws Exception { + @WithMockUser + @DisplayName("GET /user/signup returns the signup view") + void userSignupForm_ShouldReturnSignupView() throws Exception { + // Act & Assert mockMvc.perform(get("/user/signup")) .andExpect(status().isOk()) .andExpect(view().name("user/signup")); @@ -80,21 +59,38 @@ void userSignupForm_ReturnsSignupView() throws Exception { // ── POST /user/signup ───────────────────────────────────────────────────── @Test - void createUser_WithValidData_RedirectsToLogin() throws Exception { + @WithMockUser + @DisplayName("POST /user/signup with valid data redirects to the login page") + void createUser_WithValidData_ShouldRedirectToLogin() throws Exception { + // Arrange + when(userService.createUser(any())).thenReturn( + new UserDTO(1L, "New Applicant", "new@test.com", UserAuthorization.USER) + ); + + // Act & Assert mockMvc.perform(post("/user/signup") .param("fullName", "New Applicant") - .param("email", "newapplicant@controller.test") + .param("email", "new@test.com") .param("password", "securePass1") .with(csrf())) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("/user/login")); + + verify(userService).createUser(any()); } @Test - void createUser_WithDuplicateEmail_ReturnsSignupViewWithError() throws Exception { + @WithMockUser + @DisplayName("POST /user/signup with a duplicate email re-renders the signup form with an error") + void createUser_WithDuplicateEmail_ShouldReturnSignupViewWithError() throws Exception { + // Arrange + when(userService.createUser(any())) + .thenThrow(new IllegalArgumentException("A user with this email already exists")); + + // Act & Assert mockMvc.perform(post("/user/signup") .param("fullName", "Duplicate User") - .param("email", "user@controller.test") // already seeded + .param("email", "existing@test.com") .param("password", "password123") .with(csrf())) .andExpect(status().isOk()) @@ -103,10 +99,17 @@ void createUser_WithDuplicateEmail_ReturnsSignupViewWithError() throws Exception } @Test - void createUser_WithShortPassword_ReturnsSignupViewWithError() throws Exception { + @WithMockUser + @DisplayName("POST /user/signup with a short password re-renders the signup form with an error") + void createUser_WithShortPassword_ShouldReturnSignupViewWithError() throws Exception { + // Arrange + when(userService.createUser(any())) + .thenThrow(new IllegalArgumentException("Password must be at least 8 characters")); + + // Act & Assert mockMvc.perform(post("/user/signup") .param("fullName", "Short Pass") - .param("email", "shortpass@controller.test") + .param("email", "short@test.com") .param("password", "abc") .with(csrf())) .andExpect(status().isOk()) @@ -117,7 +120,10 @@ void createUser_WithShortPassword_ReturnsSignupViewWithError() throws Exception // ── GET /user/login ─────────────────────────────────────────────────────── @Test - void userLoginForm_ReturnsLoginView() throws Exception { + @WithMockUser + @DisplayName("GET /user/login returns the login view") + void userLoginForm_ShouldReturnLoginView() throws Exception { + // Act & Assert mockMvc.perform(get("/user/login")) .andExpect(status().isOk()) .andExpect(view().name("user/login")); @@ -126,9 +132,16 @@ void userLoginForm_ReturnsLoginView() throws Exception { // ── GET /profile/view/{userId} ──────────────────────────────────────────── @Test - void viewProfile_AsOwnUser_ReturnsProfileViewWithCanEditTrue() throws Exception { - mockMvc.perform(get("/profile/view/" + savedUser.getId()) - .with(authentication(authFor(savedUser)))) + @DisplayName("GET /profile/view/{id} viewed by the profile owner shows 'canEdit = true'") + void viewProfile_AsOwnUser_ShouldReturnProfileViewWithCanEditTrue() throws Exception { + // Arrange + Long userId = 1L; + UserDTO userDTO = new UserDTO(userId, "Test User", "user@test.com", UserAuthorization.USER); + when(userService.findById(userId)).thenReturn(Optional.of(userDTO)); + + // Act & Assert + mockMvc.perform(get("/profile/view/" + userId) + .with(authentication(authFor(userId, "Test User", "user@test.com", UserAuthorization.USER)))) .andExpect(status().isOk()) .andExpect(view().name("profile/view")) .andExpect(model().attributeExists("user")) @@ -136,73 +149,109 @@ void viewProfile_AsOwnUser_ReturnsProfileViewWithCanEditTrue() throws Exception } @Test - void viewProfile_AsSysAdminViewingOtherUser_ReturnsProfileViewWithCanEditTrue() throws Exception { - mockMvc.perform(get("/profile/view/" + savedUser.getId()) - .with(authentication(authFor(savedSysAdmin)))) + @DisplayName("GET /profile/view/{id} viewed by a SYSADMIN shows 'canEdit = true'") + void viewProfile_AsSysAdmin_ShouldReturnProfileViewWithCanEditTrue() throws Exception { + // Arrange + Long targetUserId = 1L; + Long sysAdminId = 99L; + UserDTO userDTO = new UserDTO(targetUserId, "Test User", "user@test.com", UserAuthorization.USER); + when(userService.findById(targetUserId)).thenReturn(Optional.of(userDTO)); + + // Act & Assert + mockMvc.perform(get("/profile/view/" + targetUserId) + .with(authentication(authFor(sysAdminId, "SysAdmin", "sysadmin@test.com", UserAuthorization.SYSADMIN)))) .andExpect(status().isOk()) .andExpect(view().name("profile/view")) .andExpect(model().attribute("canEdit", true)); } @Test - void viewProfile_AsAdminViewingOtherUser_ReturnsForbidden() throws Exception { - mockMvc.perform(get("/profile/view/" + savedSysAdmin.getId()) - .with(authentication(authFor(savedAdmin)))) - .andExpect(status().isForbidden()); - } + @DisplayName("GET /profile/view/{id} returns 403 when a regular user tries to view another user's profile") + void viewProfile_AsUnauthorizedUser_ShouldReturnForbidden() throws Exception { + // Arrange + Long targetUserId = 999L; - @Test - void viewProfile_AsUserViewingOtherProfile_ReturnsForbidden() throws Exception { - mockMvc.perform(get("/profile/view/" + savedAdmin.getId()) - .with(authentication(authFor(savedUser)))) + doThrow(new UnauthorizedException("You do not have permission to edit this profile.")) + .when(userService).validateProfileAccess(any(SecurityUser.class), eq(targetUserId)); + + UserDTO userDTO = new UserDTO(targetUserId, "Other User", "other@test.com", UserAuthorization.USER); + when(userService.findById(targetUserId)).thenReturn(Optional.of(userDTO)); + + // Act & Assert + mockMvc.perform(get("/profile/view/" + targetUserId) + .with(authentication(authFor(1L, "Test User", "user@test.com", UserAuthorization.USER)))) .andExpect(status().isForbidden()); } // ── GET /profile/edit/{userId} ──────────────────────────────────────────── @Test - void showProfileEditForm_AsOwnUser_ReturnsEditViewWithUserModel() throws Exception { - mockMvc.perform(get("/profile/edit/" + savedUser.getId()) - .with(authentication(authFor(savedUser)))) + @DisplayName("GET /profile/edit/{id} returns the edit form when accessed by the profile owner") + void showProfileEditForm_AsOwnUser_ShouldReturnEditView() throws Exception { + // Arrange + Long userId = 1L; + UserDTO userDTO = new UserDTO(userId, "Test User", "user@test.com", UserAuthorization.USER); + when(userService.findById(userId)).thenReturn(Optional.of(userDTO)); + + // Act & Assert + mockMvc.perform(get("/profile/edit/" + userId) + .with(authentication(authFor(userId, "Test User", "user@test.com", UserAuthorization.USER)))) .andExpect(status().isOk()) .andExpect(view().name("profile/edit")) .andExpect(model().attributeExists("user")); } @Test - void showProfileEditForm_AsSysAdmin_ReturnsEditView() throws Exception { - mockMvc.perform(get("/profile/edit/" + savedUser.getId()) - .with(authentication(authFor(savedSysAdmin)))) - .andExpect(status().isOk()) - .andExpect(view().name("profile/edit")); - } + @DisplayName("GET /profile/edit/{id} returns 403 when accessed by an unauthorized user") + void showProfileEditForm_AsUnauthorizedUser_ShouldReturnForbidden() throws Exception { + // Arrange + Long targetUserId = 999L; - @Test - void showProfileEditForm_AsUnauthorizedUser_ReturnsForbidden() throws Exception { - mockMvc.perform(get("/profile/edit/" + savedSysAdmin.getId()) - .with(authentication(authFor(savedUser)))) + doThrow(new UnauthorizedException("You do not have permission to edit this profile.")) + .when(userService).validateProfileAccess(any(SecurityUser.class), eq(targetUserId)); + + // Act & Assert + mockMvc.perform(get("/profile/edit/" + targetUserId) + .with(authentication(authFor(1L, "Test User", "user@test.com", UserAuthorization.USER)))) .andExpect(status().isForbidden()); } // ── POST /profile/edit/{userId} ─────────────────────────────────────────── @Test - void updateProfile_WithValidData_RedirectsToProfileView() throws Exception { - mockMvc.perform(post("/profile/edit/" + savedUser.getId()) + @DisplayName("POST /profile/edit/{id} with valid data redirects to the profile view") + void updateProfile_WithValidData_ShouldRedirectToProfileView() throws Exception { + // Arrange + Long userId = 1L; + when(userService.updateUser(any())).thenReturn( + new UserDTO(userId, "Updated Name", "updated@test.com", UserAuthorization.USER) + ); + + // Act & Assert + mockMvc.perform(post("/profile/edit/" + userId) .param("fullName", "Updated Name") - .param("email", "updatedname@controller.test") - .with(authentication(authFor(savedUser))) + .param("email", "updated@test.com") + .with(authentication(authFor(userId, "Test User", "user@test.com", UserAuthorization.USER))) .with(csrf())) .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/profile/view/" + savedUser.getId())); + .andExpect(redirectedUrl("/profile/view/" + userId)); + + verify(userService).updateUser(any()); } @Test - void updateProfile_WithDuplicateEmail_ReturnsEditViewWithError() throws Exception { - mockMvc.perform(post("/profile/edit/" + savedUser.getId()) + @DisplayName("POST /profile/edit/{id} with a duplicate email re-renders the edit form with an error") + void updateProfile_WithDuplicateEmail_ShouldReturnEditViewWithError() throws Exception { + // Arrange + Long userId = 1L; + when(userService.updateUser(any())) + .thenThrow(new IllegalArgumentException("A user with this email already exists")); + + // Act & Assert + mockMvc.perform(post("/profile/edit/" + userId) .param("fullName", "Test User") - .param("email", "admin@controller.test") // already owned by savedAdmin - .with(authentication(authFor(savedUser))) + .param("email", "taken@test.com") + .with(authentication(authFor(userId, "Test User", "user@test.com", UserAuthorization.USER))) .with(csrf())) .andExpect(status().isOk()) .andExpect(view().name("profile/edit")) @@ -210,11 +259,19 @@ void updateProfile_WithDuplicateEmail_ReturnsEditViewWithError() throws Exceptio } @Test - void updateProfile_AsUnauthorizedUser_ReturnsForbidden() throws Exception { - mockMvc.perform(post("/profile/edit/" + savedAdmin.getId()) - .param("fullName", "Hacked Name") - .param("email", "hacked@controller.test") - .with(authentication(authFor(savedUser))) + @DisplayName("POST /profile/edit/{id} returns 403 when submitted by an unauthorized user") + void updateProfile_AsUnauthorizedUser_ShouldReturnForbidden() throws Exception { + // Arrange + Long targetUserId = 999L; + + doThrow(new UnauthorizedException("You do not have permission to edit this profile.")) + .when(userService).validateProfileAccess(any(SecurityUser.class), eq(targetUserId)); + + // Act & Assert + mockMvc.perform(post("/profile/edit/" + targetUserId) + .param("fullName", "Hacked") + .param("email", "hacked@test.com") + .with(authentication(authFor(1L, "Test User", "user@test.com", UserAuthorization.USER))) .with(csrf())) .andExpect(status().isForbidden()); } @@ -222,9 +279,18 @@ void updateProfile_AsUnauthorizedUser_ReturnsForbidden() throws Exception { // ── GET /user/list ──────────────────────────────────────────────────────── @Test - void userListView_AsSysAdmin_ReturnsListViewWithUsers() throws Exception { + @DisplayName("GET /user/list returns the user list view when accessed by a SYSADMIN") + void userListView_AsSysAdmin_ShouldReturnListViewWithUsers() throws Exception { + // Arrange + Long sysAdminId = 1L; + when(userService.findAll()).thenReturn(List.of( + new UserDTO(1L, "SysAdmin", "sysadmin@test.com", UserAuthorization.SYSADMIN), + new UserDTO(2L, "User", "user@test.com", UserAuthorization.USER) + )); + + // Act & Assert mockMvc.perform(get("/user/list") - .with(authentication(authFor(savedSysAdmin)))) + .with(authentication(authFor(sysAdminId, "SysAdmin", "sysadmin@test.com", UserAuthorization.SYSADMIN)))) .andExpect(status().isOk()) .andExpect(view().name("user/list")) .andExpect(model().attributeExists("users")) @@ -232,30 +298,51 @@ void userListView_AsSysAdmin_ReturnsListViewWithUsers() throws Exception { } @Test - void userListView_AsNonSysAdmin_ReturnsForbidden() throws Exception { + @DisplayName("GET /user/list returns 403 when accessed by a non-SYSADMIN") + void userListView_AsNonSysAdmin_ShouldReturnForbidden() throws Exception { + // Arrange + doThrow(new UnauthorizedException("Only system administrators can access this page.")) + .when(userService).validateSysAdmin(any(SecurityUser.class)); + + // Act & Assert mockMvc.perform(get("/user/list") - .with(authentication(authFor(savedUser)))) + .with(authentication(authFor(1L, "Test User", "user@test.com", UserAuthorization.USER)))) .andExpect(status().isForbidden()); } // ── GET /dashboard/applicant ────────────────────────────────────────────── @Test - void applicantDashboard_AsUser_ReturnsDashboardViewWithVisas() throws Exception { + @DisplayName("GET /dashboard/applicant returns the applicant dashboard with their visa list") + void applicantDashboard_AsUser_ShouldReturnDashboardWithVisas() throws Exception { + // Arrange + Long userId = 1L; + when(visaService.findVisasByApplicantId(userId)).thenReturn(List.of()); + + // Act & Assert mockMvc.perform(get("/dashboard/applicant") - .with(authentication(authFor(savedUser)))) + .with(authentication(authFor(userId, "Test User", "user@test.com", UserAuthorization.USER)))) .andExpect(status().isOk()) .andExpect(view().name("dashboard/applicant")) .andExpect(model().attributeExists("visas")) .andExpect(model().attributeExists("name")); + + verify(visaService).findVisasByApplicantId(userId); } // ── GET /dashboard/admin ────────────────────────────────────────────────── @Test - void adminDashboard_AsAdmin_ReturnsDashboardViewWithCases() throws Exception { + @DisplayName("GET /dashboard/admin returns assigned and unassigned cases for an ADMIN") + void adminDashboard_AsAdmin_ShouldReturnDashboardWithCases() throws Exception { + // Arrange + Long adminId = 1L; + when(visaService.findVisasByHandlerId(adminId)).thenReturn(List.of()); + when(visaService.findVisaByStatus("UNASSIGNED")).thenReturn(List.of()); + + // Act & Assert mockMvc.perform(get("/dashboard/admin") - .with(authentication(authFor(savedAdmin)))) + .with(authentication(authFor(adminId, "Test Admin", "admin@test.com", UserAuthorization.ADMIN)))) .andExpect(status().isOk()) .andExpect(view().name("dashboard/admin")) .andExpect(model().attributeExists("assignedCases")) @@ -264,26 +351,31 @@ void adminDashboard_AsAdmin_ReturnsDashboardViewWithCases() throws Exception { } @Test - void adminDashboard_AsSysAdmin_ReturnsDashboardView() throws Exception { - mockMvc.perform(get("/dashboard/admin") - .with(authentication(authFor(savedSysAdmin)))) - .andExpect(status().isOk()) - .andExpect(view().name("dashboard/admin")); - } + @DisplayName("GET /dashboard/admin returns 403 when accessed by a regular USER") + void adminDashboard_AsRegularUser_ShouldReturnForbidden() throws Exception { + // Arrange + doThrow(new UnauthorizedException("Only administrators or system administrators can access this page.")) + .when(userService).validateAdmin(any(SecurityUser.class)); - @Test - void adminDashboard_AsRegularUser_ReturnsForbidden() throws Exception { + // Act & Assert mockMvc.perform(get("/dashboard/admin") - .with(authentication(authFor(savedUser)))) + .with(authentication(authFor(1L, "Test User", "user@test.com", UserAuthorization.USER)))) .andExpect(status().isForbidden()); } // ── GET /dashboard/sysadmin ─────────────────────────────────────────────── @Test - void sysAdminDashboard_AsSysAdmin_ReturnsDashboardViewWithUsersAndAuditLogs() throws Exception { + @DisplayName("GET /dashboard/sysadmin returns users and audit logs for a SYSADMIN") + void sysAdminDashboard_AsSysAdmin_ShouldReturnDashboardWithUsersAndLogs() throws Exception { + // Arrange + Long sysAdminId = 1L; + when(userService.findAll()).thenReturn(List.of()); + when(auditService.findAll()).thenReturn(List.of()); + + // Act & Assert mockMvc.perform(get("/dashboard/sysadmin") - .with(authentication(authFor(savedSysAdmin)))) + .with(authentication(authFor(sysAdminId, "SysAdmin", "sysadmin@test.com", UserAuthorization.SYSADMIN)))) .andExpect(status().isOk()) .andExpect(view().name("dashboard/sysadmin")) .andExpect(model().attributeExists("users")) @@ -292,16 +384,41 @@ void sysAdminDashboard_AsSysAdmin_ReturnsDashboardViewWithUsersAndAuditLogs() th } @Test - void sysAdminDashboard_AsAdmin_ReturnsForbidden() throws Exception { + @DisplayName("GET /dashboard/sysadmin returns 403 when accessed by an ADMIN") + void sysAdminDashboard_AsAdmin_ShouldReturnForbidden() throws Exception { + // Arrange + doThrow(new UnauthorizedException("Only system administrators can access this page.")) + .when(userService).validateSysAdmin(any(SecurityUser.class)); + + // Act & Assert mockMvc.perform(get("/dashboard/sysadmin") - .with(authentication(authFor(savedAdmin)))) + .with(authentication(authFor(1L, "Test Admin", "admin@test.com", UserAuthorization.ADMIN)))) .andExpect(status().isForbidden()); } @Test - void sysAdminDashboard_AsRegularUser_ReturnsForbidden() throws Exception { + @DisplayName("GET /dashboard/sysadmin returns 403 when accessed by a regular USER") + void sysAdminDashboard_AsRegularUser_ShouldReturnForbidden() throws Exception { + // Arrange + doThrow(new UnauthorizedException("Only system administrators can access this page.")) + .when(userService).validateSysAdmin(any(SecurityUser.class)); + + // Act & Assert mockMvc.perform(get("/dashboard/sysadmin") - .with(authentication(authFor(savedUser)))) + .with(authentication(authFor(1L, "Test User", "user@test.com", UserAuthorization.USER)))) .andExpect(status().isForbidden()); } + + // ── Helper methods ──────────────────────────────────────────────────────── + + private Authentication authFor(Long id, String fullName, String email, UserAuthorization auth) { + User user = new User(); + user.setId(id); + user.setFullName(fullName); + user.setEmail(email); + user.setPassword("password"); + user.setUserAuthorization(auth); + SecurityUser principal = new SecurityUser(user); + return new UsernamePasswordAuthenticationToken(principal, null, principal.getAuthorities()); + } } diff --git a/src/test/java/org/example/visacasemanagementsystem/user/mapper/UserMapperTest.java b/src/test/java/org/example/visacasemanagementsystem/user/mapper/UserMapperTest.java index 1e49359..06d3cff 100644 --- a/src/test/java/org/example/visacasemanagementsystem/user/mapper/UserMapperTest.java +++ b/src/test/java/org/example/visacasemanagementsystem/user/mapper/UserMapperTest.java @@ -6,10 +6,13 @@ import org.example.visacasemanagementsystem.user.dto.UserDTO; import org.example.visacasemanagementsystem.user.entity.User; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +@DisplayName("UserMapper unit tests") class UserMapperTest { private UserMapper userMapper; @@ -19,49 +22,37 @@ void setUp() { userMapper = new UserMapper(); } - // ── toDTO ───────────────────────────────────────────────────────────────── - @Test - void toDTO_WithValidUser_ReturnsMappedUserDTO() { + @DisplayName("toDTO maps a User entity to a UserDTO with all fields preserved") + void shouldMapUserEntityToUserDTO() { + // Arrange User user = new User(); user.setFullName("Alice Wonderland"); user.setEmail("alice@example.com"); user.setPassword("password123"); user.setUserAuthorization(UserAuthorization.USER); + // Act UserDTO dto = userMapper.toDTO(user); - assertNotNull(dto); - assertEquals("Alice Wonderland", dto.fullName()); - assertEquals("alice@example.com", dto.email()); - assertEquals(UserAuthorization.USER, dto.userAuthorization()); + // Assert + assertThat(dto).isNotNull(); + assertThat(dto.fullName()).isEqualTo("Alice Wonderland"); + assertThat(dto.email()).isEqualTo("alice@example.com"); + assertThat(dto.userAuthorization()).isEqualTo(UserAuthorization.USER); } @Test - void toDTO_WithAllAuthorizationLevels_MapsCorrectly() { - for (UserAuthorization auth : UserAuthorization.values()) { - User user = new User(); - user.setFullName("Test User"); - user.setEmail("test@example.com"); - user.setPassword("password123"); - user.setUserAuthorization(auth); - - UserDTO dto = userMapper.toDTO(user); - - assertNotNull(dto); - assertEquals(auth, dto.userAuthorization()); - } + @DisplayName("toDTO returns null when the input User is null") + void shouldReturnNullDTO_WhenUserIsNull() { + // Act & Assert + assertThat(userMapper.toDTO(null)).isNull(); } @Test - void toDTO_WithNullUser_ReturnsNull() { - assertNull(userMapper.toDTO(null)); - } - - // ── toEntity ────────────────────────────────────────────────────────────── - - @Test - void toEntity_WithValidCreateUserDTO_ReturnsMappedUser() { + @DisplayName("toEntity maps a CreateUserDTO to a User entity with correct name, email, and authorization") + void shouldMapCreateUserDTOToUserEntity() { + // Arrange CreateUserDTO dto = new CreateUserDTO( "Bob Builder", "bob@example.com", @@ -69,17 +60,20 @@ void toEntity_WithValidCreateUserDTO_ReturnsMappedUser() { UserAuthorization.ADMIN ); + // Act User user = userMapper.toEntity(dto); - assertNotNull(user); - assertEquals("Bob Builder", user.getFullName()); - assertEquals("bob@example.com", user.getEmail()); - assertEquals(UserAuthorization.ADMIN, user.getUserAuthorization()); + // Assert + assertThat(user).isNotNull(); + assertThat(user.getFullName()).isEqualTo("Bob Builder"); + assertThat(user.getEmail()).isEqualTo("bob@example.com"); + assertThat(user.getUserAuthorization()).isEqualTo(UserAuthorization.ADMIN); } @Test - void toEntity_DoesNotSetPassword() { - // The mapper intentionally leaves password unset; UserService sets it separately + @DisplayName("toEntity does not set the password field (UserService handles passwords separately)") + void shouldNotSetPassword_WhenMappingFromCreateUserDTO() { + // Arrange CreateUserDTO dto = new CreateUserDTO( "No Pass", "nopass@example.com", @@ -87,21 +81,25 @@ void toEntity_DoesNotSetPassword() { UserAuthorization.USER ); + // Act User user = userMapper.toEntity(dto); - assertNotNull(user); - assertNull(user.getPassword()); + // Assert + assertThat(user).isNotNull(); + assertThat(user.getPassword()).isNull(); } @Test - void toEntity_WithNullCreateUserDTO_ReturnsNull() { - assertNull(userMapper.toEntity(null)); + @DisplayName("toEntity returns null when the input CreateUserDTO is null") + void shouldReturnNullEntity_WhenCreateUserDTOIsNull() { + // Act & Assert + assertThat(userMapper.toEntity(null)).isNull(); } - // ── updateEntityFromDTO ─────────────────────────────────────────────────── - @Test - void updateEntityFromDTO_WithValidInputs_UpdatesFullNameAndEmail() { + @DisplayName("updateEntityFromDTO overwrites fullName and email on an existing User entity") + void shouldUpdateExistingUserEntityFromUpdateUserDTO() { + // Arrange User user = new User(); user.setFullName("Old Name"); user.setEmail("old@example.com"); @@ -109,14 +107,19 @@ void updateEntityFromDTO_WithValidInputs_UpdatesFullNameAndEmail() { user.setUserAuthorization(UserAuthorization.USER); UpdateUserDTO dto = new UpdateUserDTO(1L, "New Name", "new@example.com"); + + // Act userMapper.updateEntityFromDTO(dto, user); - assertEquals("New Name", user.getFullName()); - assertEquals("new@example.com", user.getEmail()); + // Assert + assertThat(user.getFullName()).isEqualTo("New Name"); + assertThat(user.getEmail()).isEqualTo("new@example.com"); } @Test - void updateEntityFromDTO_DoesNotModifyAuthorizationOrPassword() { + @DisplayName("updateEntityFromDTO does not modify authorization or password") + void shouldNotModifyAuthorizationOrPassword_WhenUpdatingFromDTO() { + // Arrange User user = new User(); user.setFullName("Original"); user.setEmail("original@example.com"); @@ -124,33 +127,39 @@ void updateEntityFromDTO_DoesNotModifyAuthorizationOrPassword() { user.setUserAuthorization(UserAuthorization.SYSADMIN); UpdateUserDTO dto = new UpdateUserDTO(1L, "Changed Name", "changed@example.com"); + + // Act userMapper.updateEntityFromDTO(dto, user); - // Authorization and password must remain untouched - assertEquals(UserAuthorization.SYSADMIN, user.getUserAuthorization()); - assertEquals("secret123", user.getPassword()); + // Assert + assertThat(user.getUserAuthorization()).isEqualTo(UserAuthorization.SYSADMIN); + assertThat(user.getPassword()).isEqualTo("secret123"); } @Test - void updateEntityFromDTO_WithNullDTO_DoesNotUpdateEntity() { + @DisplayName("updateEntityFromDTO leaves the entity unchanged when the DTO is null") + void shouldNotUpdateEntity_WhenUpdateDTOIsNull() { + // Arrange User user = new User(); user.setFullName("Original Name"); user.setEmail("original@example.com"); + // Act userMapper.updateEntityFromDTO(null, user); - assertEquals("Original Name", user.getFullName()); - assertEquals("original@example.com", user.getEmail()); + // Assert + assertThat(user.getFullName()).isEqualTo("Original Name"); + assertThat(user.getEmail()).isEqualTo("original@example.com"); } @Test - void updateEntityFromDTO_WithNullUser_DoesNotThrow() { + @DisplayName("updateEntityFromDTO does not throw when the User entity is null") + void shouldNotThrow_WhenUserIsNullInUpdate() { + // Arrange UpdateUserDTO dto = new UpdateUserDTO(1L, "Some Name", "some@example.com"); - assertDoesNotThrow(() -> userMapper.updateEntityFromDTO(dto, null)); - } - @Test - void updateEntityFromDTO_WithBothNull_DoesNotThrow() { - assertDoesNotThrow(() -> userMapper.updateEntityFromDTO(null, null)); + // Act & Assert + assertThatCode(() -> userMapper.updateEntityFromDTO(dto, null)) + .doesNotThrowAnyException(); } } diff --git a/src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceIntegrationTest.java b/src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceIntegrationTest.java new file mode 100644 index 0000000..de6d1dc --- /dev/null +++ b/src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceIntegrationTest.java @@ -0,0 +1,241 @@ +package org.example.visacasemanagementsystem.user.service; + +import org.example.visacasemanagementsystem.exception.UnauthorizedException; +import org.example.visacasemanagementsystem.user.UserAuthorization; +import org.example.visacasemanagementsystem.user.dto.CreateUserDTO; +import org.example.visacasemanagementsystem.user.dto.UpdateUserDTO; +import org.example.visacasemanagementsystem.user.dto.UserDTO; +import org.example.visacasemanagementsystem.user.entity.User; +import org.example.visacasemanagementsystem.user.repository.UserRepository; +import org.example.visacasemanagementsystem.user.security.SecurityUser; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +@SpringBootTest +@Transactional +@ActiveProfiles("test") +@DisplayName("UserService integration tests (H2)") +class UserServiceIntegrationTest { + + @Autowired + private UserService userService; + + @Autowired + private UserRepository userRepository; + + @Test + @DisplayName("createUser persists and returns the new user when all input is valid") + void createUser_shouldSaveUser_WhenDataIsValid() { + // Arrange + CreateUserDTO dto = new CreateUserDTO( + "New Applicant", + "newapplicant@integration.test", + "securePass1", + UserAuthorization.USER + ); + + // Act + UserDTO result = userService.createUser(dto); + + // Assert + assertThat(result).isNotNull(); + assertThat(result.id()).isNotNull(); + assertThat(result.fullName()).isEqualTo("New Applicant"); + assertThat(result.email()).isEqualTo("newapplicant@integration.test"); + + var savedUsers = userRepository.findAll(); + assertThat(savedUsers).anyMatch(u -> u.getEmail().equals("newapplicant@integration.test")); + } + + @Test + @DisplayName("updateUser changes fullName and email in the database") + void updateUser_shouldUpdateUserFields_WhenDataIsValid() { + // Arrange + User user = createAndSaveValidUser(); + UpdateUserDTO dto = new UpdateUserDTO(user.getId(), "Updated Name", "updated@integration.test"); + + // Act + UserDTO result = userService.updateUser(dto); + + // Assert + assertThat(result.fullName()).isEqualTo("Updated Name"); + assertThat(result.email()).isEqualTo("updated@integration.test"); + + User updatedUser = userRepository.findById(user.getId()).orElseThrow(); + assertThat(updatedUser.getFullName()).isEqualTo("Updated Name"); + assertThat(updatedUser.getEmail()).isEqualTo("updated@integration.test"); + } + + @Test + @DisplayName("updateUserAuthorization promotes a USER to ADMIN when requested by a SYSADMIN") + void updateUserAuthorization_shouldChangeAuthorization_WhenRequestedBySysAdmin() { + // Arrange + User sysAdmin = createAndSaveUser("SysAdmin", UserAuthorization.SYSADMIN); + User targetUser = createAndSaveValidUser(); + + // Act + UserDTO result = userService.updateUserAuthorization( + targetUser.getId(), UserAuthorization.ADMIN, sysAdmin.getId() + ); + + // Assert + assertThat(result.userAuthorization()).isEqualTo(UserAuthorization.ADMIN); + + User updated = userRepository.findById(targetUser.getId()).orElseThrow(); + assertThat(updated.getUserAuthorization()).isEqualTo(UserAuthorization.ADMIN); + } + + @Test + @DisplayName("deleteUser removes the user row from the database when requested by a SYSADMIN") + void deleteUser_shouldRemoveUser_WhenRequestedBySysAdmin() { + // Arrange + User sysAdmin = createAndSaveUser("SysAdmin", UserAuthorization.SYSADMIN); + User targetUser = createAndSaveValidUser(); + Long targetId = targetUser.getId(); + + // Act + userService.deleteUser(targetId, sysAdmin.getId()); + + // Assert + assertThat(userRepository.findById(targetId)).isEmpty(); + } + + @Test + @DisplayName("findAll returns a list that includes every seeded user") + void findAll_shouldReturnAllUsers() { + // Arrange + createAndSaveValidUser(); + createAndSaveUser("Admin", UserAuthorization.ADMIN); + + // Act + var result = userService.findAll(); + + // Assert + assertThat(result).hasSizeGreaterThanOrEqualTo(2); + } + + @Test + @DisplayName("findById returns the correct user when the ID exists") + void findById_shouldReturnUser_WhenUserExists() { + // Arrange + User user = createAndSaveValidUser(); + + // Act + var result = userService.findById(user.getId()); + + // Assert + assertThat(result).isPresent(); + assertThat(result.get().fullName()).isEqualTo("Test User"); + } + + @Test + @DisplayName("findByEmail returns the correct user when the email exists") + void findByEmail_shouldReturnUser_WhenEmailExists() { + // Arrange + User user = createAndSaveValidUser(); + + // Act + var result = userService.findByEmail(user.getEmail()); + + // Assert + assertThat(result).isPresent(); + assertThat(result.get().id()).isEqualTo(user.getId()); + } + + @Test + @DisplayName("validateProfileAccess allows a user to view their own profile") + void validateProfileAccess_shouldNotThrow_WhenAccessingOwnProfile() { + // Arrange + User user = createAndSaveValidUser(); + SecurityUser principal = new SecurityUser(user); + + // Act & Assert — should complete without exception + userService.validateProfileAccess(principal, user.getId()); + } + + @Test + @DisplayName("validateProfileAccess allows a SYSADMIN to view any profile") + void validateProfileAccess_shouldNotThrow_WhenSysAdminAccessesOtherProfile() { + // Arrange + User sysAdmin = createAndSaveUser("SysAdmin", UserAuthorization.SYSADMIN); + User otherUser = createAndSaveValidUser(); + SecurityUser principal = new SecurityUser(sysAdmin); + + // Act & Assert — should complete without exception + userService.validateProfileAccess(principal, otherUser.getId()); + } + + @Test + @DisplayName("validateProfileAccess rejects a regular user viewing another user's profile") + void validateProfileAccess_shouldThrowUnauthorizedException_WhenUserAccessesOtherProfile() { + // Arrange + User user = createAndSaveValidUser(); + User otherUser = createAndSaveUser("Other", UserAuthorization.ADMIN); + SecurityUser principal = new SecurityUser(user); + + // Act & Assert + Long otherUserId = otherUser.getId(); + assertThatExceptionOfType(UnauthorizedException.class) + .isThrownBy(() -> userService.validateProfileAccess(principal, otherUserId)); + } + + @Test + @DisplayName("validateSysAdmin passes when principal has the SYSADMIN role") + void validateSysAdmin_shouldNotThrow_WhenPrincipalIsSysAdmin() { + // Arrange + User sysAdmin = createAndSaveUser("SysAdmin", UserAuthorization.SYSADMIN); + SecurityUser principal = new SecurityUser(sysAdmin); + + // Act & Assert — should complete without exception + userService.validateSysAdmin(principal); + } + + @Test + @DisplayName("validateAdmin passes when principal has the ADMIN role") + void validateAdmin_shouldNotThrow_WhenPrincipalIsAdmin() { + // Arrange + User admin = createAndSaveUser("Admin", UserAuthorization.ADMIN); + SecurityUser principal = new SecurityUser(admin); + + // Act & Assert — should complete without exception + userService.validateAdmin(principal); + } + + @Test + @DisplayName("validateAdmin also passes when principal has the SYSADMIN role") + void validateAdmin_shouldNotThrow_WhenPrincipalIsSysAdmin() { + // Arrange + User sysAdmin = createAndSaveUser("SysAdmin", UserAuthorization.SYSADMIN); + SecurityUser principal = new SecurityUser(sysAdmin); + + // Act & Assert — should complete without exception + userService.validateAdmin(principal); + } + + // ── Helper methods ──────────────────────────────────────────────────────── + + private User createAndSaveValidUser() { + User user = new User(); + user.setFullName("Test User"); + user.setEmail(java.util.UUID.randomUUID() + "@test.com"); + user.setPassword("password123"); + user.setUserAuthorization(UserAuthorization.USER); + return userRepository.save(user); + } + + private User createAndSaveUser(String name, UserAuthorization auth) { + User user = new User(); + user.setFullName(name); + user.setEmail(java.util.UUID.randomUUID() + "@test.com"); + user.setPassword("password123"); + user.setUserAuthorization(auth); + return userRepository.save(user); + } +} diff --git a/src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceTest.java b/src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceTest.java index 6af1da6..30a4b8a 100644 --- a/src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceTest.java +++ b/src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceTest.java @@ -1,325 +1,386 @@ package org.example.visacasemanagementsystem.user.service; import jakarta.persistence.EntityNotFoundException; -import org.example.visacasemanagementsystem.TestcontainersConfiguration; import org.example.visacasemanagementsystem.exception.UnauthorizedException; import org.example.visacasemanagementsystem.user.UserAuthorization; import org.example.visacasemanagementsystem.user.dto.CreateUserDTO; import org.example.visacasemanagementsystem.user.dto.UpdateUserDTO; import org.example.visacasemanagementsystem.user.dto.UserDTO; import org.example.visacasemanagementsystem.user.entity.User; +import org.example.visacasemanagementsystem.user.mapper.UserMapper; import org.example.visacasemanagementsystem.user.repository.UserRepository; import org.example.visacasemanagementsystem.user.security.SecurityUser; -import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Import; -import org.springframework.transaction.annotation.Transactional; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.dao.DataIntegrityViolationException; -import java.util.List; import java.util.Optional; -import static org.junit.jupiter.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.*; -@Import(TestcontainersConfiguration.class) -@SpringBootTest -@Transactional +@ExtendWith(MockitoExtension.class) +@DisplayName("UserService unit tests") class UserServiceTest { - @Autowired - private UserService userService; - - @Autowired + @Mock private UserRepository userRepository; + @Mock + private UserMapper userMapper; - private User savedUser; - private User savedAdmin; - private User savedSysAdmin; - - @BeforeEach - void setUp() { - User user = new User(); - user.setFullName("Test User"); - user.setEmail("user@service.test"); - user.setPassword("password123"); - user.setUserAuthorization(UserAuthorization.USER); - savedUser = userRepository.save(user); - - User admin = new User(); - admin.setFullName("Test Admin"); - admin.setEmail("admin@service.test"); - admin.setPassword("password123"); - admin.setUserAuthorization(UserAuthorization.ADMIN); - savedAdmin = userRepository.save(admin); - - User sysadmin = new User(); - sysadmin.setFullName("Test SysAdmin"); - sysadmin.setEmail("sysadmin@service.test"); - sysadmin.setPassword("password123"); - sysadmin.setUserAuthorization(UserAuthorization.SYSADMIN); - savedSysAdmin = userRepository.save(sysadmin); - } - - // ── findAll ─────────────────────────────────────────────────────────────── - - @Test - void findAll_ReturnsListContainingAllSeededUsers() { - List result = userService.findAll(); - - assertNotNull(result); - assertTrue(result.size() >= 3); - assertTrue(result.stream().anyMatch(u -> u.email().equals("user@service.test"))); - assertTrue(result.stream().anyMatch(u -> u.email().equals("admin@service.test"))); - assertTrue(result.stream().anyMatch(u -> u.email().equals("sysadmin@service.test"))); - } - - // ── findById ────────────────────────────────────────────────────────────── - - @Test - void findById_WithExistingId_ReturnsMatchingUser() { - Optional result = userService.findById(savedUser.getId()); + @InjectMocks + private UserService userService; - assertTrue(result.isPresent()); - assertEquals("Test User", result.get().fullName()); - assertEquals("user@service.test", result.get().email()); - assertEquals(UserAuthorization.USER, result.get().userAuthorization()); - } + // ── createUser ──────────────────────────────────────────────────────────── @Test - void findById_WithNonExistingId_ReturnsEmpty() { - Optional result = userService.findById(999999L); - assertFalse(result.isPresent()); - } - - // ── findByEmail ─────────────────────────────────────────────────────────── + @DisplayName("createUser throws IllegalArgumentException when password is shorter than 8 characters") + void createUser_shouldThrowIllegalArgumentException_WhenPasswordIsTooShort() { + // Arrange + CreateUserDTO dto = new CreateUserDTO( + "Short Pass", "short@test.com", "abc", UserAuthorization.USER + ); - @Test - void findByEmail_WithExistingEmail_ReturnsMatchingUser() { - Optional result = userService.findByEmail("admin@service.test"); + when(userMapper.toEntity(dto)).thenReturn(new User()); - assertTrue(result.isPresent()); - assertEquals("Test Admin", result.get().fullName()); - assertEquals(UserAuthorization.ADMIN, result.get().userAuthorization()); - } + // Act & Assert + assertThatThrownBy(() -> userService.createUser(dto)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("8 characters"); - @Test - void findByEmail_WithNonExistingEmail_ReturnsEmpty() { - Optional result = userService.findByEmail("nobody@nowhere.test"); - assertFalse(result.isPresent()); + // Verify — repository should never be called + verifyNoInteractions(userRepository); } - // ── createUser ──────────────────────────────────────────────────────────── - @Test - void createUser_WithValidData_PersistsAndReturnsNewUser() { + @DisplayName("createUser throws IllegalArgumentException when email is already taken") + void createUser_shouldThrowIllegalArgumentException_WhenEmailAlreadyExists() { + // Arrange CreateUserDTO dto = new CreateUserDTO( - "New Applicant", - "newapplicant@service.test", - "securePass1", - UserAuthorization.USER + "Duplicate", "existing@test.com", "password123", UserAuthorization.USER ); - UserDTO result = userService.createUser(dto); + User mappedUser = new User(); + when(userMapper.toEntity(dto)).thenReturn(mappedUser); + when(userRepository.save(any(User.class))) + .thenThrow(new DataIntegrityViolationException("unique constraint")); - assertNotNull(result); - assertNotNull(result.id()); - assertEquals("New Applicant", result.fullName()); - assertEquals("newapplicant@service.test", result.email()); + // Act & Assert + assertThatThrownBy(() -> userService.createUser(dto)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("email already exists"); } @Test - void createUser_WithDuplicateEmail_ThrowsIllegalArgumentException() { + @DisplayName("createUser persists and returns a UserDTO when input is valid") + void createUser_shouldSaveAndReturnUserDTO_WhenDataIsValid() { + // Arrange CreateUserDTO dto = new CreateUserDTO( - "Duplicate", - "user@service.test", - "password123", - UserAuthorization.USER + "New User", "new@test.com", "password123", UserAuthorization.USER ); - assertThrows(IllegalArgumentException.class, () -> userService.createUser(dto)); - } + User mappedUser = new User(); + User savedUser = new User(); + savedUser.setId(1L); + UserDTO expectedDTO = new UserDTO(1L, "New User", "new@test.com", UserAuthorization.USER); - @Test - void createUser_WithShortPassword_ThrowsIllegalArgumentException() { - CreateUserDTO dto = new CreateUserDTO( - "Short Pass", - "shortpass@service.test", - "abc", - UserAuthorization.USER - ); + when(userMapper.toEntity(dto)).thenReturn(mappedUser); + when(userRepository.save(any(User.class))).thenReturn(savedUser); + when(userMapper.toDTO(savedUser)).thenReturn(expectedDTO); - IllegalArgumentException ex = assertThrows( - IllegalArgumentException.class, - () -> userService.createUser(dto) - ); - assertTrue(ex.getMessage().contains("8 characters")); + // Act + UserDTO result = userService.createUser(dto); + + // Assert + assertThat(result).isEqualTo(expectedDTO); + verify(userRepository, times(1)).save(any(User.class)); } // ── updateUser ──────────────────────────────────────────────────────────── @Test - void updateUser_WithValidData_UpdatesAndReturnsUser() { - UpdateUserDTO dto = new UpdateUserDTO( - savedUser.getId(), - "Updated Name", - "updated@service.test" - ); - - UserDTO result = userService.updateUser(dto); - - assertNotNull(result); - assertEquals("Updated Name", result.fullName()); - assertEquals("updated@service.test", result.email()); + @DisplayName("updateUser throws EntityNotFoundException when user ID does not exist") + void updateUser_shouldThrowEntityNotFoundException_WhenUserDoesNotExist() { + // Arrange + UpdateUserDTO dto = new UpdateUserDTO(999L, "Name", "email@test.com"); + when(userRepository.findById(999L)).thenReturn(Optional.empty()); + + // Act & Assert + assertThatThrownBy(() -> userService.updateUser(dto)) + .isInstanceOf(EntityNotFoundException.class) + .hasMessage("User not found"); } @Test - void updateUser_WithNonExistingId_ThrowsEntityNotFoundException() { - UpdateUserDTO dto = new UpdateUserDTO(999999L, "Name", "missing@service.test"); - - assertThrows(EntityNotFoundException.class, () -> userService.updateUser(dto)); + @DisplayName("updateUser throws IllegalArgumentException when new email is already taken by another user") + void updateUser_shouldThrowIllegalArgumentException_WhenEmailAlreadyExists() { + // Arrange + Long userId = 1L; + UpdateUserDTO dto = new UpdateUserDTO(userId, "User", "taken@test.com"); + + User existingUser = new User(); + existingUser.setId(userId); + + when(userRepository.findById(userId)).thenReturn(Optional.of(existingUser)); + when(userRepository.saveAndFlush(any(User.class))) + .thenThrow(new DataIntegrityViolationException("unique constraint")); + + // Act & Assert + assertThatThrownBy(() -> userService.updateUser(dto)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("email already exists"); } @Test - void updateUser_WithEmailAlreadyOwnedByAnotherUser_ThrowsIllegalArgumentException() { - // Try to update savedUser's email to admin's existing email - UpdateUserDTO dto = new UpdateUserDTO( - savedUser.getId(), - "Test User", - "admin@service.test" - ); + @DisplayName("updateUser updates fields and returns the updated UserDTO") + void updateUser_shouldUpdateAndReturnUser_WhenDataIsValid() { + // Arrange + Long userId = 1L; + UpdateUserDTO dto = new UpdateUserDTO(userId, "Updated Name", "updated@test.com"); - assertThrows(IllegalArgumentException.class, () -> userService.updateUser(dto)); + User existingUser = new User(); + existingUser.setId(userId); + UserDTO expectedDTO = new UserDTO(userId, "Updated Name", "updated@test.com", UserAuthorization.USER); + + when(userRepository.findById(userId)).thenReturn(Optional.of(existingUser)); + when(userRepository.saveAndFlush(any(User.class))).thenReturn(existingUser); + when(userMapper.toDTO(existingUser)).thenReturn(expectedDTO); + + // Act + UserDTO result = userService.updateUser(dto); + + // Assert + assertThat(result).isEqualTo(expectedDTO); + verify(userMapper).updateEntityFromDTO(dto, existingUser); + verify(userRepository).saveAndFlush(existingUser); } // ── updateUserAuthorization ─────────────────────────────────────────────── @Test - void updateUserAuthorization_BySysAdmin_ChangesAuthorizationLevel() { - UserDTO result = userService.updateUserAuthorization( - savedUser.getId(), - UserAuthorization.ADMIN, - savedSysAdmin.getId() - ); - - assertEquals(UserAuthorization.ADMIN, result.userAuthorization()); + @DisplayName("updateUserAuthorization throws UnauthorizedException when requester is not a SYSADMIN") + void updateUserAuthorization_shouldThrowUnauthorizedException_WhenRequesterIsNotSysAdmin() { + // Arrange + Long userId = 1L; + Long requesterId = 2L; + + User requester = new User(); + requester.setId(requesterId); + requester.setUserAuthorization(UserAuthorization.USER); + + when(userRepository.findById(requesterId)).thenReturn(Optional.of(requester)); + + // Act & Assert + assertThatThrownBy(() -> userService.updateUserAuthorization(userId, UserAuthorization.ADMIN, requesterId)) + .isInstanceOf(UnauthorizedException.class) + .hasMessageContaining("not authorized"); } @Test - void updateUserAuthorization_ByNonSysAdmin_ThrowsUnauthorizedException() { - assertThrows(UnauthorizedException.class, () -> - userService.updateUserAuthorization( - savedAdmin.getId(), - UserAuthorization.SYSADMIN, - savedUser.getId() - ) - ); + @DisplayName("updateUserAuthorization throws EntityNotFoundException when target user does not exist") + void updateUserAuthorization_shouldThrowEntityNotFoundException_WhenTargetUserDoesNotExist() { + // Arrange + Long nonExistingUserId = 999L; + Long sysAdminId = 1L; + + User sysAdmin = new User(); + sysAdmin.setId(sysAdminId); + sysAdmin.setUserAuthorization(UserAuthorization.SYSADMIN); + + when(userRepository.findById(sysAdminId)).thenReturn(Optional.of(sysAdmin)); + when(userRepository.findById(nonExistingUserId)).thenReturn(Optional.empty()); + + // Act & Assert + assertThatThrownBy(() -> userService.updateUserAuthorization(nonExistingUserId, UserAuthorization.ADMIN, sysAdminId)) + .isInstanceOf(EntityNotFoundException.class) + .hasMessage("User not found"); } @Test - void updateUserAuthorization_WithNonExistingTargetUser_ThrowsEntityNotFoundException() { - assertThrows(EntityNotFoundException.class, () -> - userService.updateUserAuthorization( - 999999L, - UserAuthorization.ADMIN, - savedSysAdmin.getId() - ) - ); - } + @DisplayName("updateUserAuthorization changes role when requested by a SYSADMIN") + void updateUserAuthorization_shouldChangeAuthorization_WhenRequesterIsSysAdmin() { + // Arrange + Long userId = 1L; + Long sysAdminId = 2L; - // ── deleteUser ──────────────────────────────────────────────────────────── + User sysAdmin = new User(); + sysAdmin.setId(sysAdminId); + sysAdmin.setUserAuthorization(UserAuthorization.SYSADMIN); - @Test - void deleteUser_BySysAdmin_RemovesUserFromDatabase() { - Long userId = savedUser.getId(); + User targetUser = new User(); + targetUser.setId(userId); + targetUser.setUserAuthorization(UserAuthorization.USER); - userService.deleteUser(userId, savedSysAdmin.getId()); + UserDTO expectedDTO = new UserDTO(userId, "User", "user@test.com", UserAuthorization.ADMIN); - assertFalse(userRepository.findById(userId).isPresent()); - } + when(userRepository.findById(sysAdminId)).thenReturn(Optional.of(sysAdmin)); + when(userRepository.findById(userId)).thenReturn(Optional.of(targetUser)); + when(userRepository.save(any(User.class))).thenReturn(targetUser); + when(userMapper.toDTO(targetUser)).thenReturn(expectedDTO); - @Test - void deleteUser_ByNonSysAdmin_ThrowsUnauthorizedException() { - assertThrows(UnauthorizedException.class, () -> - userService.deleteUser(savedAdmin.getId(), savedUser.getId()) - ); - } + // Act + UserDTO result = userService.updateUserAuthorization(userId, UserAuthorization.ADMIN, sysAdminId); - @Test - void deleteUser_WithNonExistingUser_ThrowsEntityNotFoundException() { - assertThrows(EntityNotFoundException.class, () -> - userService.deleteUser(999999L, savedSysAdmin.getId()) - ); + // Assert + assertThat(result.userAuthorization()).isEqualTo(UserAuthorization.ADMIN); + verify(userRepository).save(targetUser); } - // ── validateProfileAccess ───────────────────────────────────────────────── + // ── deleteUser ──────────────────────────────────────────────────────────── @Test - void validateProfileAccess_OwnProfile_DoesNotThrow() { - SecurityUser principal = new SecurityUser(savedUser); + @DisplayName("deleteUser throws UnauthorizedException when requester is not a SYSADMIN") + void deleteUser_shouldThrowUnauthorizedException_WhenRequesterIsNotSysAdmin() { + // Arrange + Long userId = 1L; + Long requesterId = 2L; + + User requester = new User(); + requester.setId(requesterId); + requester.setUserAuthorization(UserAuthorization.ADMIN); + + when(userRepository.findById(requesterId)).thenReturn(Optional.of(requester)); - assertDoesNotThrow(() -> userService.validateProfileAccess(principal, savedUser.getId())); + // Act & Assert + assertThatThrownBy(() -> userService.deleteUser(userId, requesterId)) + .isInstanceOf(UnauthorizedException.class) + .hasMessageContaining("not authorized"); + + verify(userRepository, never()).delete(any(User.class)); } @Test - void validateProfileAccess_SysAdminViewingOtherProfile_DoesNotThrow() { - SecurityUser principal = new SecurityUser(savedSysAdmin); - - assertDoesNotThrow(() -> userService.validateProfileAccess(principal, savedUser.getId())); + @DisplayName("deleteUser throws EntityNotFoundException when target user does not exist") + void deleteUser_shouldThrowEntityNotFoundException_WhenTargetUserDoesNotExist() { + // Arrange + Long nonExistingUserId = 999L; + Long sysAdminId = 1L; + + User sysAdmin = new User(); + sysAdmin.setId(sysAdminId); + sysAdmin.setUserAuthorization(UserAuthorization.SYSADMIN); + + when(userRepository.findById(sysAdminId)).thenReturn(Optional.of(sysAdmin)); + when(userRepository.findById(nonExistingUserId)).thenReturn(Optional.empty()); + + // Act & Assert + assertThatThrownBy(() -> userService.deleteUser(nonExistingUserId, sysAdminId)) + .isInstanceOf(EntityNotFoundException.class) + .hasMessage("User not found"); } @Test - void validateProfileAccess_UserViewingOtherProfile_ThrowsUnauthorizedException() { - SecurityUser principal = new SecurityUser(savedUser); + @DisplayName("deleteUser removes the user when requested by a SYSADMIN") + void deleteUser_shouldDeleteUser_WhenRequesterIsSysAdmin() { + // Arrange + Long userId = 1L; + Long sysAdminId = 2L; - assertThrows(UnauthorizedException.class, () -> - userService.validateProfileAccess(principal, savedAdmin.getId()) - ); - } + User sysAdmin = new User(); + sysAdmin.setId(sysAdminId); + sysAdmin.setUserAuthorization(UserAuthorization.SYSADMIN); - // ── validateSysAdmin (SecurityUser overload) ────────────────────────────── + User targetUser = new User(); + targetUser.setId(userId); - @Test - void validateSysAdmin_WithSysAdminPrincipal_DoesNotThrow() { - SecurityUser principal = new SecurityUser(savedSysAdmin); + when(userRepository.findById(sysAdminId)).thenReturn(Optional.of(sysAdmin)); + when(userRepository.findById(userId)).thenReturn(Optional.of(targetUser)); - assertDoesNotThrow(() -> userService.validateSysAdmin(principal)); + // Act + userService.deleteUser(userId, sysAdminId); + + // Assert + verify(userRepository, times(1)).delete(targetUser); } + // ── validateProfileAccess ───────────────────────────────────────────────── + @Test - void validateSysAdmin_WithAdminPrincipal_ThrowsUnauthorizedException() { - SecurityUser principal = new SecurityUser(savedAdmin); + @DisplayName("validateProfileAccess throws UnauthorizedException when a regular user accesses another user's profile") + void validateProfileAccess_shouldThrowUnauthorizedException_WhenUserAccessesOtherProfile() { + // Arrange + User user = new User(); + user.setId(1L); + user.setFullName("Regular User"); + user.setEmail("user@test.com"); + user.setPassword("password"); + user.setUserAuthorization(UserAuthorization.USER); + SecurityUser principal = new SecurityUser(user); - assertThrows(UnauthorizedException.class, () -> userService.validateSysAdmin(principal)); + Long otherUserId = 999L; + + // Act & Assert + assertThatThrownBy(() -> userService.validateProfileAccess(principal, otherUserId)) + .isInstanceOf(UnauthorizedException.class) + .hasMessageContaining("permission"); } + // ── validateSysAdmin (SecurityUser) ─────────────────────────────────────── + @Test - void validateSysAdmin_WithUserPrincipal_ThrowsUnauthorizedException() { - SecurityUser principal = new SecurityUser(savedUser); + @DisplayName("validateSysAdmin throws UnauthorizedException when principal is not a SYSADMIN") + void validateSysAdmin_shouldThrowUnauthorizedException_WhenPrincipalIsNotSysAdmin() { + // Arrange + User user = new User(); + user.setId(1L); + user.setFullName("Regular User"); + user.setEmail("user@test.com"); + user.setPassword("password"); + user.setUserAuthorization(UserAuthorization.USER); + SecurityUser principal = new SecurityUser(user); - assertThrows(UnauthorizedException.class, () -> userService.validateSysAdmin(principal)); + // Act & Assert + assertThatThrownBy(() -> userService.validateSysAdmin(principal)) + .isInstanceOf(UnauthorizedException.class) + .hasMessageContaining("system administrators"); } // ── validateAdmin ───────────────────────────────────────────────────────── @Test - void validateAdmin_WithAdminPrincipal_DoesNotThrow() { - SecurityUser principal = new SecurityUser(savedAdmin); + @DisplayName("validateAdmin throws UnauthorizedException when principal is a regular USER") + void validateAdmin_shouldThrowUnauthorizedException_WhenPrincipalIsRegularUser() { + // Arrange + User user = new User(); + user.setId(1L); + user.setFullName("Regular User"); + user.setEmail("user@test.com"); + user.setPassword("password"); + user.setUserAuthorization(UserAuthorization.USER); + SecurityUser principal = new SecurityUser(user); - assertDoesNotThrow(() -> userService.validateAdmin(principal)); + // Act & Assert + assertThatThrownBy(() -> userService.validateAdmin(principal)) + .isInstanceOf(UnauthorizedException.class) + .hasMessageContaining("administrators"); } + // ── findById ────────────────────────────────────────────────────────────── + @Test - void validateAdmin_WithSysAdminPrincipal_DoesNotThrow() { - SecurityUser principal = new SecurityUser(savedSysAdmin); + @DisplayName("findById returns empty Optional when user ID does not exist") + void findById_shouldReturnEmpty_WhenUserDoesNotExist() { + // Arrange + when(userRepository.findById(999L)).thenReturn(Optional.empty()); - assertDoesNotThrow(() -> userService.validateAdmin(principal)); + // Act & Assert + assertThat(userService.findById(999L)).isEmpty(); } + // ── findByEmail ─────────────────────────────────────────────────────────── + @Test - void validateAdmin_WithUserPrincipal_ThrowsUnauthorizedException() { - SecurityUser principal = new SecurityUser(savedUser); + @DisplayName("findByEmail returns empty Optional when email does not exist") + void findByEmail_shouldReturnEmpty_WhenEmailDoesNotExist() { + // Arrange + when(userRepository.findByEmail("nobody@test.com")).thenReturn(Optional.empty()); - assertThrows(UnauthorizedException.class, () -> userService.validateAdmin(principal)); + // Act & Assert + assertThat(userService.findByEmail("nobody@test.com")).isEmpty(); } } From b621d027533946fdaa87b1ec9f299de16e0f528b Mon Sep 17 00:00:00 2001 From: Martin Karlsson Date: Mon, 20 Apr 2026 15:14:01 +0200 Subject: [PATCH 3/7] Fix: - Updated User Tests to use UserPrincipal instead of SecurityUSer --- .../controller/UserViewControllerTest.java | 61 ++++++++++--------- .../user/mapper/UserMapperTest.java | 25 +++++--- .../service/UserServiceIntegrationTest.java | 48 ++++++++------- .../user/service/UserServiceTest.java | 52 +++++++++------- 4 files changed, 103 insertions(+), 83 deletions(-) diff --git a/src/test/java/org/example/visacasemanagementsystem/user/controller/UserViewControllerTest.java b/src/test/java/org/example/visacasemanagementsystem/user/controller/UserViewControllerTest.java index f198305..b25d507 100644 --- a/src/test/java/org/example/visacasemanagementsystem/user/controller/UserViewControllerTest.java +++ b/src/test/java/org/example/visacasemanagementsystem/user/controller/UserViewControllerTest.java @@ -5,7 +5,7 @@ import org.example.visacasemanagementsystem.user.UserAuthorization; import org.example.visacasemanagementsystem.user.dto.UserDTO; import org.example.visacasemanagementsystem.user.entity.User; -import org.example.visacasemanagementsystem.user.security.SecurityUser; +import org.example.visacasemanagementsystem.user.security.UserPrincipal; import org.example.visacasemanagementsystem.user.service.UserService; import org.example.visacasemanagementsystem.visa.service.VisaService; import org.junit.jupiter.api.DisplayName; @@ -48,7 +48,7 @@ class UserViewControllerTest { @Test @WithMockUser - @DisplayName("GET /user/signup returns the signup view") + @DisplayName("Checking if GET /user/signup returns the signup view") void userSignupForm_ShouldReturnSignupView() throws Exception { // Act & Assert mockMvc.perform(get("/user/signup")) @@ -60,7 +60,7 @@ void userSignupForm_ShouldReturnSignupView() throws Exception { @Test @WithMockUser - @DisplayName("POST /user/signup with valid data redirects to the login page") + @DisplayName("Checking if POST /user/signup with valid data redirects to the login page") void createUser_WithValidData_ShouldRedirectToLogin() throws Exception { // Arrange when(userService.createUser(any())).thenReturn( @@ -81,7 +81,7 @@ void createUser_WithValidData_ShouldRedirectToLogin() throws Exception { @Test @WithMockUser - @DisplayName("POST /user/signup with a duplicate email re-renders the signup form with an error") + @DisplayName("Checking if POST /user/signup with a duplicate email re-renders the signup form with an error") void createUser_WithDuplicateEmail_ShouldReturnSignupViewWithError() throws Exception { // Arrange when(userService.createUser(any())) @@ -100,7 +100,7 @@ void createUser_WithDuplicateEmail_ShouldReturnSignupViewWithError() throws Exce @Test @WithMockUser - @DisplayName("POST /user/signup with a short password re-renders the signup form with an error") + @DisplayName("Checking if POST /user/signup with a short password re-renders the signup form with an error") void createUser_WithShortPassword_ShouldReturnSignupViewWithError() throws Exception { // Arrange when(userService.createUser(any())) @@ -121,7 +121,7 @@ void createUser_WithShortPassword_ShouldReturnSignupViewWithError() throws Excep @Test @WithMockUser - @DisplayName("GET /user/login returns the login view") + @DisplayName("Checking if GET /user/login returns the login view") void userLoginForm_ShouldReturnLoginView() throws Exception { // Act & Assert mockMvc.perform(get("/user/login")) @@ -132,7 +132,7 @@ void userLoginForm_ShouldReturnLoginView() throws Exception { // ── GET /profile/view/{userId} ──────────────────────────────────────────── @Test - @DisplayName("GET /profile/view/{id} viewed by the profile owner shows 'canEdit = true'") + @DisplayName("Checking if GET /profile/view/{id} viewed by the profile owner shows 'canEdit = true'") void viewProfile_AsOwnUser_ShouldReturnProfileViewWithCanEditTrue() throws Exception { // Arrange Long userId = 1L; @@ -149,7 +149,7 @@ void viewProfile_AsOwnUser_ShouldReturnProfileViewWithCanEditTrue() throws Excep } @Test - @DisplayName("GET /profile/view/{id} viewed by a SYSADMIN shows 'canEdit = true'") + @DisplayName("Checking if GET /profile/view/{id} viewed by a SYSADMIN shows 'canEdit = true'") void viewProfile_AsSysAdmin_ShouldReturnProfileViewWithCanEditTrue() throws Exception { // Arrange Long targetUserId = 1L; @@ -166,13 +166,13 @@ void viewProfile_AsSysAdmin_ShouldReturnProfileViewWithCanEditTrue() throws Exce } @Test - @DisplayName("GET /profile/view/{id} returns 403 when a regular user tries to view another user's profile") + @DisplayName("Checking if GET /profile/view/{id} returns 403 when a regular user tries to view another user's profile") void viewProfile_AsUnauthorizedUser_ShouldReturnForbidden() throws Exception { // Arrange Long targetUserId = 999L; doThrow(new UnauthorizedException("You do not have permission to edit this profile.")) - .when(userService).validateProfileAccess(any(SecurityUser.class), eq(targetUserId)); + .when(userService).validateProfileAccess(any(UserPrincipal.class), eq(targetUserId)); UserDTO userDTO = new UserDTO(targetUserId, "Other User", "other@test.com", UserAuthorization.USER); when(userService.findById(targetUserId)).thenReturn(Optional.of(userDTO)); @@ -186,7 +186,7 @@ void viewProfile_AsUnauthorizedUser_ShouldReturnForbidden() throws Exception { // ── GET /profile/edit/{userId} ──────────────────────────────────────────── @Test - @DisplayName("GET /profile/edit/{id} returns the edit form when accessed by the profile owner") + @DisplayName("Checking if GET /profile/edit/{id} returns the edit form when accessed by the profile owner") void showProfileEditForm_AsOwnUser_ShouldReturnEditView() throws Exception { // Arrange Long userId = 1L; @@ -202,13 +202,13 @@ void showProfileEditForm_AsOwnUser_ShouldReturnEditView() throws Exception { } @Test - @DisplayName("GET /profile/edit/{id} returns 403 when accessed by an unauthorized user") + @DisplayName("Checking if GET /profile/edit/{id} returns 403 when accessed by an unauthorized user") void showProfileEditForm_AsUnauthorizedUser_ShouldReturnForbidden() throws Exception { // Arrange Long targetUserId = 999L; doThrow(new UnauthorizedException("You do not have permission to edit this profile.")) - .when(userService).validateProfileAccess(any(SecurityUser.class), eq(targetUserId)); + .when(userService).validateProfileAccess(any(UserPrincipal.class), eq(targetUserId)); // Act & Assert mockMvc.perform(get("/profile/edit/" + targetUserId) @@ -219,7 +219,7 @@ void showProfileEditForm_AsUnauthorizedUser_ShouldReturnForbidden() throws Excep // ── POST /profile/edit/{userId} ─────────────────────────────────────────── @Test - @DisplayName("POST /profile/edit/{id} with valid data redirects to the profile view") + @DisplayName("Checking if POST /profile/edit/{id} with valid data redirects to the profile view") void updateProfile_WithValidData_ShouldRedirectToProfileView() throws Exception { // Arrange Long userId = 1L; @@ -240,7 +240,7 @@ void updateProfile_WithValidData_ShouldRedirectToProfileView() throws Exception } @Test - @DisplayName("POST /profile/edit/{id} with a duplicate email re-renders the edit form with an error") + @DisplayName("Checking if POST /profile/edit/{id} with a duplicate email re-renders the edit form with an error") void updateProfile_WithDuplicateEmail_ShouldReturnEditViewWithError() throws Exception { // Arrange Long userId = 1L; @@ -259,13 +259,13 @@ void updateProfile_WithDuplicateEmail_ShouldReturnEditViewWithError() throws Exc } @Test - @DisplayName("POST /profile/edit/{id} returns 403 when submitted by an unauthorized user") + @DisplayName("Checking if POST /profile/edit/{id} returns 403 when submitted by an unauthorized user") void updateProfile_AsUnauthorizedUser_ShouldReturnForbidden() throws Exception { // Arrange Long targetUserId = 999L; doThrow(new UnauthorizedException("You do not have permission to edit this profile.")) - .when(userService).validateProfileAccess(any(SecurityUser.class), eq(targetUserId)); + .when(userService).validateProfileAccess(any(UserPrincipal.class), eq(targetUserId)); // Act & Assert mockMvc.perform(post("/profile/edit/" + targetUserId) @@ -279,7 +279,7 @@ void updateProfile_AsUnauthorizedUser_ShouldReturnForbidden() throws Exception { // ── GET /user/list ──────────────────────────────────────────────────────── @Test - @DisplayName("GET /user/list returns the user list view when accessed by a SYSADMIN") + @DisplayName("Checking if GET /user/list returns the user list view when accessed by a SYSADMIN") void userListView_AsSysAdmin_ShouldReturnListViewWithUsers() throws Exception { // Arrange Long sysAdminId = 1L; @@ -298,11 +298,11 @@ void userListView_AsSysAdmin_ShouldReturnListViewWithUsers() throws Exception { } @Test - @DisplayName("GET /user/list returns 403 when accessed by a non-SYSADMIN") + @DisplayName("Checking if GET /user/list returns 403 when accessed by a non-SYSADMIN") void userListView_AsNonSysAdmin_ShouldReturnForbidden() throws Exception { // Arrange doThrow(new UnauthorizedException("Only system administrators can access this page.")) - .when(userService).validateSysAdmin(any(SecurityUser.class)); + .when(userService).validateSysAdmin(any(UserPrincipal.class)); // Act & Assert mockMvc.perform(get("/user/list") @@ -313,7 +313,7 @@ void userListView_AsNonSysAdmin_ShouldReturnForbidden() throws Exception { // ── GET /dashboard/applicant ────────────────────────────────────────────── @Test - @DisplayName("GET /dashboard/applicant returns the applicant dashboard with their visa list") + @DisplayName("Checking if GET /dashboard/applicant returns the applicant dashboard with their visa list") void applicantDashboard_AsUser_ShouldReturnDashboardWithVisas() throws Exception { // Arrange Long userId = 1L; @@ -333,7 +333,7 @@ void applicantDashboard_AsUser_ShouldReturnDashboardWithVisas() throws Exception // ── GET /dashboard/admin ────────────────────────────────────────────────── @Test - @DisplayName("GET /dashboard/admin returns assigned and unassigned cases for an ADMIN") + @DisplayName("Checking if GET /dashboard/admin returns assigned and unassigned cases for an ADMIN") void adminDashboard_AsAdmin_ShouldReturnDashboardWithCases() throws Exception { // Arrange Long adminId = 1L; @@ -351,11 +351,11 @@ void adminDashboard_AsAdmin_ShouldReturnDashboardWithCases() throws Exception { } @Test - @DisplayName("GET /dashboard/admin returns 403 when accessed by a regular USER") + @DisplayName("Checking if GET /dashboard/admin returns 403 when accessed by a regular USER") void adminDashboard_AsRegularUser_ShouldReturnForbidden() throws Exception { // Arrange doThrow(new UnauthorizedException("Only administrators or system administrators can access this page.")) - .when(userService).validateAdmin(any(SecurityUser.class)); + .when(userService).validateAdmin(any(UserPrincipal.class)); // Act & Assert mockMvc.perform(get("/dashboard/admin") @@ -366,7 +366,7 @@ void adminDashboard_AsRegularUser_ShouldReturnForbidden() throws Exception { // ── GET /dashboard/sysadmin ─────────────────────────────────────────────── @Test - @DisplayName("GET /dashboard/sysadmin returns users and audit logs for a SYSADMIN") + @DisplayName("Checking if GET /dashboard/sysadmin returns users and audit logs for a SYSADMIN") void sysAdminDashboard_AsSysAdmin_ShouldReturnDashboardWithUsersAndLogs() throws Exception { // Arrange Long sysAdminId = 1L; @@ -384,11 +384,11 @@ void sysAdminDashboard_AsSysAdmin_ShouldReturnDashboardWithUsersAndLogs() throws } @Test - @DisplayName("GET /dashboard/sysadmin returns 403 when accessed by an ADMIN") + @DisplayName("Checking if GET /dashboard/sysadmin returns 403 when accessed by an ADMIN") void sysAdminDashboard_AsAdmin_ShouldReturnForbidden() throws Exception { // Arrange doThrow(new UnauthorizedException("Only system administrators can access this page.")) - .when(userService).validateSysAdmin(any(SecurityUser.class)); + .when(userService).validateSysAdmin(any(UserPrincipal.class)); // Act & Assert mockMvc.perform(get("/dashboard/sysadmin") @@ -397,11 +397,11 @@ void sysAdminDashboard_AsAdmin_ShouldReturnForbidden() throws Exception { } @Test - @DisplayName("GET /dashboard/sysadmin returns 403 when accessed by a regular USER") + @DisplayName("Checking if GET /dashboard/sysadmin returns 403 when accessed by a regular USER") void sysAdminDashboard_AsRegularUser_ShouldReturnForbidden() throws Exception { // Arrange doThrow(new UnauthorizedException("Only system administrators can access this page.")) - .when(userService).validateSysAdmin(any(SecurityUser.class)); + .when(userService).validateSysAdmin(any(UserPrincipal.class)); // Act & Assert mockMvc.perform(get("/dashboard/sysadmin") @@ -416,9 +416,10 @@ private Authentication authFor(Long id, String fullName, String email, UserAutho user.setId(id); user.setFullName(fullName); user.setEmail(email); + user.setUsername(email); user.setPassword("password"); user.setUserAuthorization(auth); - SecurityUser principal = new SecurityUser(user); + UserPrincipal principal = new UserPrincipal(user); return new UsernamePasswordAuthenticationToken(principal, null, principal.getAuthorities()); } } diff --git a/src/test/java/org/example/visacasemanagementsystem/user/mapper/UserMapperTest.java b/src/test/java/org/example/visacasemanagementsystem/user/mapper/UserMapperTest.java index 06d3cff..a7494c6 100644 --- a/src/test/java/org/example/visacasemanagementsystem/user/mapper/UserMapperTest.java +++ b/src/test/java/org/example/visacasemanagementsystem/user/mapper/UserMapperTest.java @@ -23,12 +23,13 @@ void setUp() { } @Test - @DisplayName("toDTO maps a User entity to a UserDTO with all fields preserved") + @DisplayName("Checking if toDTO maps a User entity to a UserDTO with all fields preserved") void shouldMapUserEntityToUserDTO() { // Arrange User user = new User(); user.setFullName("Alice Wonderland"); user.setEmail("alice@example.com"); + user.setUsername("alice@example.com"); user.setPassword("password123"); user.setUserAuthorization(UserAuthorization.USER); @@ -43,14 +44,14 @@ void shouldMapUserEntityToUserDTO() { } @Test - @DisplayName("toDTO returns null when the input User is null") + @DisplayName("Checking if toDTO returns null when the input User is null") void shouldReturnNullDTO_WhenUserIsNull() { // Act & Assert assertThat(userMapper.toDTO(null)).isNull(); } @Test - @DisplayName("toEntity maps a CreateUserDTO to a User entity with correct name, email, and authorization") + @DisplayName("Checking if toEntity maps a CreateUserDTO to a User entity with correct name, email, username, and authorization") void shouldMapCreateUserDTOToUserEntity() { // Arrange CreateUserDTO dto = new CreateUserDTO( @@ -67,11 +68,12 @@ void shouldMapCreateUserDTOToUserEntity() { assertThat(user).isNotNull(); assertThat(user.getFullName()).isEqualTo("Bob Builder"); assertThat(user.getEmail()).isEqualTo("bob@example.com"); + assertThat(user.getUsername()).isEqualTo("bob@example.com"); assertThat(user.getUserAuthorization()).isEqualTo(UserAuthorization.ADMIN); } @Test - @DisplayName("toEntity does not set the password field (UserService handles passwords separately)") + @DisplayName("Checking if toEntity does not set the password field (UserService handles passwords separately)") void shouldNotSetPassword_WhenMappingFromCreateUserDTO() { // Arrange CreateUserDTO dto = new CreateUserDTO( @@ -90,19 +92,20 @@ void shouldNotSetPassword_WhenMappingFromCreateUserDTO() { } @Test - @DisplayName("toEntity returns null when the input CreateUserDTO is null") + @DisplayName("Checking if toEntity returns null when the input CreateUserDTO is null") void shouldReturnNullEntity_WhenCreateUserDTOIsNull() { // Act & Assert assertThat(userMapper.toEntity(null)).isNull(); } @Test - @DisplayName("updateEntityFromDTO overwrites fullName and email on an existing User entity") + @DisplayName("Checking if updateEntityFromDTO overwrites fullName, email, and username on an existing User entity") void shouldUpdateExistingUserEntityFromUpdateUserDTO() { // Arrange User user = new User(); user.setFullName("Old Name"); user.setEmail("old@example.com"); + user.setUsername("old@example.com"); user.setPassword("password123"); user.setUserAuthorization(UserAuthorization.USER); @@ -114,15 +117,17 @@ void shouldUpdateExistingUserEntityFromUpdateUserDTO() { // Assert assertThat(user.getFullName()).isEqualTo("New Name"); assertThat(user.getEmail()).isEqualTo("new@example.com"); + assertThat(user.getUsername()).isEqualTo("new@example.com"); } @Test - @DisplayName("updateEntityFromDTO does not modify authorization or password") + @DisplayName("Checking if updateEntityFromDTO does not modify authorization or password") void shouldNotModifyAuthorizationOrPassword_WhenUpdatingFromDTO() { // Arrange User user = new User(); user.setFullName("Original"); user.setEmail("original@example.com"); + user.setUsername("original@example.com"); user.setPassword("secret123"); user.setUserAuthorization(UserAuthorization.SYSADMIN); @@ -137,12 +142,13 @@ void shouldNotModifyAuthorizationOrPassword_WhenUpdatingFromDTO() { } @Test - @DisplayName("updateEntityFromDTO leaves the entity unchanged when the DTO is null") + @DisplayName("Checking if updateEntityFromDTO leaves the entity unchanged when the DTO is null") void shouldNotUpdateEntity_WhenUpdateDTOIsNull() { // Arrange User user = new User(); user.setFullName("Original Name"); user.setEmail("original@example.com"); + user.setUsername("original@example.com"); // Act userMapper.updateEntityFromDTO(null, user); @@ -150,10 +156,11 @@ void shouldNotUpdateEntity_WhenUpdateDTOIsNull() { // Assert assertThat(user.getFullName()).isEqualTo("Original Name"); assertThat(user.getEmail()).isEqualTo("original@example.com"); + assertThat(user.getUsername()).isEqualTo("original@example.com"); } @Test - @DisplayName("updateEntityFromDTO does not throw when the User entity is null") + @DisplayName("Checking if updateEntityFromDTO does not throw when the User entity is null") void shouldNotThrow_WhenUserIsNullInUpdate() { // Arrange UpdateUserDTO dto = new UpdateUserDTO(1L, "Some Name", "some@example.com"); diff --git a/src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceIntegrationTest.java b/src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceIntegrationTest.java index de6d1dc..7145dd0 100644 --- a/src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceIntegrationTest.java +++ b/src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceIntegrationTest.java @@ -7,7 +7,7 @@ import org.example.visacasemanagementsystem.user.dto.UserDTO; import org.example.visacasemanagementsystem.user.entity.User; import org.example.visacasemanagementsystem.user.repository.UserRepository; -import org.example.visacasemanagementsystem.user.security.SecurityUser; +import org.example.visacasemanagementsystem.user.security.UserPrincipal; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -31,7 +31,7 @@ class UserServiceIntegrationTest { private UserRepository userRepository; @Test - @DisplayName("createUser persists and returns the new user when all input is valid") + @DisplayName("Checking if createUser persists and returns the new user when all input is valid") void createUser_shouldSaveUser_WhenDataIsValid() { // Arrange CreateUserDTO dto = new CreateUserDTO( @@ -55,7 +55,7 @@ void createUser_shouldSaveUser_WhenDataIsValid() { } @Test - @DisplayName("updateUser changes fullName and email in the database") + @DisplayName("Checking if updateUser changes fullName and email in the database") void updateUser_shouldUpdateUserFields_WhenDataIsValid() { // Arrange User user = createAndSaveValidUser(); @@ -74,7 +74,7 @@ void updateUser_shouldUpdateUserFields_WhenDataIsValid() { } @Test - @DisplayName("updateUserAuthorization promotes a USER to ADMIN when requested by a SYSADMIN") + @DisplayName("Checking if updateUserAuthorization promotes a USER to ADMIN when requested by a SYSADMIN") void updateUserAuthorization_shouldChangeAuthorization_WhenRequestedBySysAdmin() { // Arrange User sysAdmin = createAndSaveUser("SysAdmin", UserAuthorization.SYSADMIN); @@ -93,7 +93,7 @@ void updateUserAuthorization_shouldChangeAuthorization_WhenRequestedBySysAdmin() } @Test - @DisplayName("deleteUser removes the user row from the database when requested by a SYSADMIN") + @DisplayName("Checking if deleteUser removes the user row from the database when requested by a SYSADMIN") void deleteUser_shouldRemoveUser_WhenRequestedBySysAdmin() { // Arrange User sysAdmin = createAndSaveUser("SysAdmin", UserAuthorization.SYSADMIN); @@ -108,7 +108,7 @@ void deleteUser_shouldRemoveUser_WhenRequestedBySysAdmin() { } @Test - @DisplayName("findAll returns a list that includes every seeded user") + @DisplayName("Checking if findAll returns a list that includes every seeded user") void findAll_shouldReturnAllUsers() { // Arrange createAndSaveValidUser(); @@ -122,7 +122,7 @@ void findAll_shouldReturnAllUsers() { } @Test - @DisplayName("findById returns the correct user when the ID exists") + @DisplayName("Checking if findById returns the correct user when the ID exists") void findById_shouldReturnUser_WhenUserExists() { // Arrange User user = createAndSaveValidUser(); @@ -136,7 +136,7 @@ void findById_shouldReturnUser_WhenUserExists() { } @Test - @DisplayName("findByEmail returns the correct user when the email exists") + @DisplayName("Checking if findByEmail returns the correct user when the email exists") void findByEmail_shouldReturnUser_WhenEmailExists() { // Arrange User user = createAndSaveValidUser(); @@ -150,35 +150,35 @@ void findByEmail_shouldReturnUser_WhenEmailExists() { } @Test - @DisplayName("validateProfileAccess allows a user to view their own profile") + @DisplayName("Checking if validateProfileAccess allows a user to view their own profile") void validateProfileAccess_shouldNotThrow_WhenAccessingOwnProfile() { // Arrange User user = createAndSaveValidUser(); - SecurityUser principal = new SecurityUser(user); + UserPrincipal principal = new UserPrincipal(user); // Act & Assert — should complete without exception userService.validateProfileAccess(principal, user.getId()); } @Test - @DisplayName("validateProfileAccess allows a SYSADMIN to view any profile") + @DisplayName("Checking if validateProfileAccess allows a SYSADMIN to view any profile") void validateProfileAccess_shouldNotThrow_WhenSysAdminAccessesOtherProfile() { // Arrange User sysAdmin = createAndSaveUser("SysAdmin", UserAuthorization.SYSADMIN); User otherUser = createAndSaveValidUser(); - SecurityUser principal = new SecurityUser(sysAdmin); + UserPrincipal principal = new UserPrincipal(sysAdmin); // Act & Assert — should complete without exception userService.validateProfileAccess(principal, otherUser.getId()); } @Test - @DisplayName("validateProfileAccess rejects a regular user viewing another user's profile") + @DisplayName("Checking if validateProfileAccess rejects a regular user viewing another user's profile") void validateProfileAccess_shouldThrowUnauthorizedException_WhenUserAccessesOtherProfile() { // Arrange User user = createAndSaveValidUser(); User otherUser = createAndSaveUser("Other", UserAuthorization.ADMIN); - SecurityUser principal = new SecurityUser(user); + UserPrincipal principal = new UserPrincipal(user); // Act & Assert Long otherUserId = otherUser.getId(); @@ -187,33 +187,33 @@ void validateProfileAccess_shouldThrowUnauthorizedException_WhenUserAccessesOthe } @Test - @DisplayName("validateSysAdmin passes when principal has the SYSADMIN role") + @DisplayName("Checking if validateSysAdmin passes when principal has the SYSADMIN role") void validateSysAdmin_shouldNotThrow_WhenPrincipalIsSysAdmin() { // Arrange User sysAdmin = createAndSaveUser("SysAdmin", UserAuthorization.SYSADMIN); - SecurityUser principal = new SecurityUser(sysAdmin); + UserPrincipal principal = new UserPrincipal(sysAdmin); // Act & Assert — should complete without exception userService.validateSysAdmin(principal); } @Test - @DisplayName("validateAdmin passes when principal has the ADMIN role") + @DisplayName("Checking if validateAdmin passes when principal has the ADMIN role") void validateAdmin_shouldNotThrow_WhenPrincipalIsAdmin() { // Arrange User admin = createAndSaveUser("Admin", UserAuthorization.ADMIN); - SecurityUser principal = new SecurityUser(admin); + UserPrincipal principal = new UserPrincipal(admin); // Act & Assert — should complete without exception userService.validateAdmin(principal); } @Test - @DisplayName("validateAdmin also passes when principal has the SYSADMIN role") + @DisplayName("Checking if validateAdmin also passes when principal has the SYSADMIN role") void validateAdmin_shouldNotThrow_WhenPrincipalIsSysAdmin() { // Arrange User sysAdmin = createAndSaveUser("SysAdmin", UserAuthorization.SYSADMIN); - SecurityUser principal = new SecurityUser(sysAdmin); + UserPrincipal principal = new UserPrincipal(sysAdmin); // Act & Assert — should complete without exception userService.validateAdmin(principal); @@ -223,8 +223,10 @@ void validateAdmin_shouldNotThrow_WhenPrincipalIsSysAdmin() { private User createAndSaveValidUser() { User user = new User(); + String uniqueEmail = java.util.UUID.randomUUID() + "@test.com"; user.setFullName("Test User"); - user.setEmail(java.util.UUID.randomUUID() + "@test.com"); + user.setEmail(uniqueEmail); + user.setUsername(uniqueEmail); user.setPassword("password123"); user.setUserAuthorization(UserAuthorization.USER); return userRepository.save(user); @@ -232,8 +234,10 @@ private User createAndSaveValidUser() { private User createAndSaveUser(String name, UserAuthorization auth) { User user = new User(); + String uniqueEmail = java.util.UUID.randomUUID() + "@test.com"; user.setFullName(name); - user.setEmail(java.util.UUID.randomUUID() + "@test.com"); + user.setEmail(uniqueEmail); + user.setUsername(uniqueEmail); user.setPassword("password123"); user.setUserAuthorization(auth); return userRepository.save(user); diff --git a/src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceTest.java b/src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceTest.java index 30a4b8a..f5d5cb6 100644 --- a/src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceTest.java +++ b/src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceTest.java @@ -9,7 +9,7 @@ import org.example.visacasemanagementsystem.user.entity.User; import org.example.visacasemanagementsystem.user.mapper.UserMapper; import org.example.visacasemanagementsystem.user.repository.UserRepository; -import org.example.visacasemanagementsystem.user.security.SecurityUser; +import org.example.visacasemanagementsystem.user.security.UserPrincipal; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -17,6 +17,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.security.crypto.password.PasswordEncoder; import java.util.Optional; @@ -32,6 +33,8 @@ class UserServiceTest { private UserRepository userRepository; @Mock private UserMapper userMapper; + @Mock + private PasswordEncoder passwordEncoder; @InjectMocks private UserService userService; @@ -39,7 +42,7 @@ class UserServiceTest { // ── createUser ──────────────────────────────────────────────────────────── @Test - @DisplayName("createUser throws IllegalArgumentException when password is shorter than 8 characters") + @DisplayName("Checking if createUser throws IllegalArgumentException when password is shorter than 8 characters") void createUser_shouldThrowIllegalArgumentException_WhenPasswordIsTooShort() { // Arrange CreateUserDTO dto = new CreateUserDTO( @@ -58,7 +61,7 @@ void createUser_shouldThrowIllegalArgumentException_WhenPasswordIsTooShort() { } @Test - @DisplayName("createUser throws IllegalArgumentException when email is already taken") + @DisplayName("Checking if createUser throws IllegalArgumentException when email is already taken") void createUser_shouldThrowIllegalArgumentException_WhenEmailAlreadyExists() { // Arrange CreateUserDTO dto = new CreateUserDTO( @@ -67,6 +70,7 @@ void createUser_shouldThrowIllegalArgumentException_WhenEmailAlreadyExists() { User mappedUser = new User(); when(userMapper.toEntity(dto)).thenReturn(mappedUser); + when(passwordEncoder.encode("password123")).thenReturn("encodedPassword"); when(userRepository.save(any(User.class))) .thenThrow(new DataIntegrityViolationException("unique constraint")); @@ -77,7 +81,7 @@ void createUser_shouldThrowIllegalArgumentException_WhenEmailAlreadyExists() { } @Test - @DisplayName("createUser persists and returns a UserDTO when input is valid") + @DisplayName("Checking if createUser persists and returns a UserDTO when input is valid") void createUser_shouldSaveAndReturnUserDTO_WhenDataIsValid() { // Arrange CreateUserDTO dto = new CreateUserDTO( @@ -90,6 +94,7 @@ void createUser_shouldSaveAndReturnUserDTO_WhenDataIsValid() { UserDTO expectedDTO = new UserDTO(1L, "New User", "new@test.com", UserAuthorization.USER); when(userMapper.toEntity(dto)).thenReturn(mappedUser); + when(passwordEncoder.encode("password123")).thenReturn("encodedPassword"); when(userRepository.save(any(User.class))).thenReturn(savedUser); when(userMapper.toDTO(savedUser)).thenReturn(expectedDTO); @@ -104,7 +109,7 @@ void createUser_shouldSaveAndReturnUserDTO_WhenDataIsValid() { // ── updateUser ──────────────────────────────────────────────────────────── @Test - @DisplayName("updateUser throws EntityNotFoundException when user ID does not exist") + @DisplayName("Checking if updateUser throws EntityNotFoundException when user ID does not exist") void updateUser_shouldThrowEntityNotFoundException_WhenUserDoesNotExist() { // Arrange UpdateUserDTO dto = new UpdateUserDTO(999L, "Name", "email@test.com"); @@ -117,7 +122,7 @@ void updateUser_shouldThrowEntityNotFoundException_WhenUserDoesNotExist() { } @Test - @DisplayName("updateUser throws IllegalArgumentException when new email is already taken by another user") + @DisplayName("Checking if updateUser throws IllegalArgumentException when new email is already taken by another user") void updateUser_shouldThrowIllegalArgumentException_WhenEmailAlreadyExists() { // Arrange Long userId = 1L; @@ -137,7 +142,7 @@ void updateUser_shouldThrowIllegalArgumentException_WhenEmailAlreadyExists() { } @Test - @DisplayName("updateUser updates fields and returns the updated UserDTO") + @DisplayName("Checking if updateUser updates fields and returns the updated UserDTO") void updateUser_shouldUpdateAndReturnUser_WhenDataIsValid() { // Arrange Long userId = 1L; @@ -163,7 +168,7 @@ void updateUser_shouldUpdateAndReturnUser_WhenDataIsValid() { // ── updateUserAuthorization ─────────────────────────────────────────────── @Test - @DisplayName("updateUserAuthorization throws UnauthorizedException when requester is not a SYSADMIN") + @DisplayName("Checking if updateUserAuthorization throws UnauthorizedException when requester is not a SYSADMIN") void updateUserAuthorization_shouldThrowUnauthorizedException_WhenRequesterIsNotSysAdmin() { // Arrange Long userId = 1L; @@ -182,7 +187,7 @@ void updateUserAuthorization_shouldThrowUnauthorizedException_WhenRequesterIsNot } @Test - @DisplayName("updateUserAuthorization throws EntityNotFoundException when target user does not exist") + @DisplayName("Checking if updateUserAuthorization throws EntityNotFoundException when target user does not exist") void updateUserAuthorization_shouldThrowEntityNotFoundException_WhenTargetUserDoesNotExist() { // Arrange Long nonExistingUserId = 999L; @@ -202,7 +207,7 @@ void updateUserAuthorization_shouldThrowEntityNotFoundException_WhenTargetUserDo } @Test - @DisplayName("updateUserAuthorization changes role when requested by a SYSADMIN") + @DisplayName("Checking if updateUserAuthorization changes role when requested by a SYSADMIN") void updateUserAuthorization_shouldChangeAuthorization_WhenRequesterIsSysAdmin() { // Arrange Long userId = 1L; @@ -234,7 +239,7 @@ void updateUserAuthorization_shouldChangeAuthorization_WhenRequesterIsSysAdmin() // ── deleteUser ──────────────────────────────────────────────────────────── @Test - @DisplayName("deleteUser throws UnauthorizedException when requester is not a SYSADMIN") + @DisplayName("Checking if deleteUser throws UnauthorizedException when requester is not a SYSADMIN") void deleteUser_shouldThrowUnauthorizedException_WhenRequesterIsNotSysAdmin() { // Arrange Long userId = 1L; @@ -255,7 +260,7 @@ void deleteUser_shouldThrowUnauthorizedException_WhenRequesterIsNotSysAdmin() { } @Test - @DisplayName("deleteUser throws EntityNotFoundException when target user does not exist") + @DisplayName("Checking if deleteUser throws EntityNotFoundException when target user does not exist") void deleteUser_shouldThrowEntityNotFoundException_WhenTargetUserDoesNotExist() { // Arrange Long nonExistingUserId = 999L; @@ -275,7 +280,7 @@ void deleteUser_shouldThrowEntityNotFoundException_WhenTargetUserDoesNotExist() } @Test - @DisplayName("deleteUser removes the user when requested by a SYSADMIN") + @DisplayName("Checking if deleteUser removes the user when requested by a SYSADMIN") void deleteUser_shouldDeleteUser_WhenRequesterIsSysAdmin() { // Arrange Long userId = 1L; @@ -301,16 +306,17 @@ void deleteUser_shouldDeleteUser_WhenRequesterIsSysAdmin() { // ── validateProfileAccess ───────────────────────────────────────────────── @Test - @DisplayName("validateProfileAccess throws UnauthorizedException when a regular user accesses another user's profile") + @DisplayName("Checking if validateProfileAccess throws UnauthorizedException when a regular user accesses another user's profile") void validateProfileAccess_shouldThrowUnauthorizedException_WhenUserAccessesOtherProfile() { // Arrange User user = new User(); user.setId(1L); user.setFullName("Regular User"); user.setEmail("user@test.com"); + user.setUsername("user@test.com"); user.setPassword("password"); user.setUserAuthorization(UserAuthorization.USER); - SecurityUser principal = new SecurityUser(user); + UserPrincipal principal = new UserPrincipal(user); Long otherUserId = 999L; @@ -320,19 +326,20 @@ void validateProfileAccess_shouldThrowUnauthorizedException_WhenUserAccessesOthe .hasMessageContaining("permission"); } - // ── validateSysAdmin (SecurityUser) ─────────────────────────────────────── + // ── validateSysAdmin (UserPrincipal) ────────────────────────────────────── @Test - @DisplayName("validateSysAdmin throws UnauthorizedException when principal is not a SYSADMIN") + @DisplayName("Checking if validateSysAdmin throws UnauthorizedException when principal is not a SYSADMIN") void validateSysAdmin_shouldThrowUnauthorizedException_WhenPrincipalIsNotSysAdmin() { // Arrange User user = new User(); user.setId(1L); user.setFullName("Regular User"); user.setEmail("user@test.com"); + user.setUsername("user@test.com"); user.setPassword("password"); user.setUserAuthorization(UserAuthorization.USER); - SecurityUser principal = new SecurityUser(user); + UserPrincipal principal = new UserPrincipal(user); // Act & Assert assertThatThrownBy(() -> userService.validateSysAdmin(principal)) @@ -343,16 +350,17 @@ void validateSysAdmin_shouldThrowUnauthorizedException_WhenPrincipalIsNotSysAdmi // ── validateAdmin ───────────────────────────────────────────────────────── @Test - @DisplayName("validateAdmin throws UnauthorizedException when principal is a regular USER") + @DisplayName("Checking if validateAdmin throws UnauthorizedException when principal is a regular USER") void validateAdmin_shouldThrowUnauthorizedException_WhenPrincipalIsRegularUser() { // Arrange User user = new User(); user.setId(1L); user.setFullName("Regular User"); user.setEmail("user@test.com"); + user.setUsername("user@test.com"); user.setPassword("password"); user.setUserAuthorization(UserAuthorization.USER); - SecurityUser principal = new SecurityUser(user); + UserPrincipal principal = new UserPrincipal(user); // Act & Assert assertThatThrownBy(() -> userService.validateAdmin(principal)) @@ -363,7 +371,7 @@ void validateAdmin_shouldThrowUnauthorizedException_WhenPrincipalIsRegularUser() // ── findById ────────────────────────────────────────────────────────────── @Test - @DisplayName("findById returns empty Optional when user ID does not exist") + @DisplayName("Checking if findById returns empty Optional when user ID does not exist") void findById_shouldReturnEmpty_WhenUserDoesNotExist() { // Arrange when(userRepository.findById(999L)).thenReturn(Optional.empty()); @@ -375,7 +383,7 @@ void findById_shouldReturnEmpty_WhenUserDoesNotExist() { // ── findByEmail ─────────────────────────────────────────────────────────── @Test - @DisplayName("findByEmail returns empty Optional when email does not exist") + @DisplayName("Checking if findByEmail returns empty Optional when email does not exist") void findByEmail_shouldReturnEmpty_WhenEmailDoesNotExist() { // Arrange when(userRepository.findByEmail("nobody@test.com")).thenReturn(Optional.empty()); From 7efd8c0441780b47257d1066ed2cf5258143b314 Mon Sep 17 00:00:00 2001 From: Martin Karlsson Date: Tue, 21 Apr 2026 10:08:41 +0200 Subject: [PATCH 4/7] Fix: - Made more tests function with the most recent security update --- .../controller/UserViewControllerTest.java | 30 ++--- .../service/UserServiceIntegrationTest.java | 42 +----- .../user/service/UserServiceTest.java | 123 ++---------------- 3 files changed, 22 insertions(+), 173 deletions(-) diff --git a/src/test/java/org/example/visacasemanagementsystem/user/controller/UserViewControllerTest.java b/src/test/java/org/example/visacasemanagementsystem/user/controller/UserViewControllerTest.java index b25d507..5046b26 100644 --- a/src/test/java/org/example/visacasemanagementsystem/user/controller/UserViewControllerTest.java +++ b/src/test/java/org/example/visacasemanagementsystem/user/controller/UserViewControllerTest.java @@ -13,6 +13,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.core.Authentication; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.bean.override.mockito.MockitoBean; @@ -31,6 +32,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @WebMvcTest(UserViewController.class) +@EnableMethodSecurity @DisplayName("UserViewController web-layer tests") class UserViewControllerTest { @@ -300,11 +302,7 @@ void userListView_AsSysAdmin_ShouldReturnListViewWithUsers() throws Exception { @Test @DisplayName("Checking if GET /user/list returns 403 when accessed by a non-SYSADMIN") void userListView_AsNonSysAdmin_ShouldReturnForbidden() throws Exception { - // Arrange - doThrow(new UnauthorizedException("Only system administrators can access this page.")) - .when(userService).validateSysAdmin(any(UserPrincipal.class)); - - // Act & Assert + // Act & Assert — @PreAuthorize("hasRole('SYSADMIN')") rejects USER mockMvc.perform(get("/user/list") .with(authentication(authFor(1L, "Test User", "user@test.com", UserAuthorization.USER)))) .andExpect(status().isForbidden()); @@ -338,7 +336,7 @@ void adminDashboard_AsAdmin_ShouldReturnDashboardWithCases() throws Exception { // Arrange Long adminId = 1L; when(visaService.findVisasByHandlerId(adminId)).thenReturn(List.of()); - when(visaService.findVisaByStatus("UNASSIGNED")).thenReturn(List.of()); + when(visaService.findVisaByStatus("SUBMITTED")).thenReturn(List.of()); // Act & Assert mockMvc.perform(get("/dashboard/admin") @@ -353,11 +351,7 @@ void adminDashboard_AsAdmin_ShouldReturnDashboardWithCases() throws Exception { @Test @DisplayName("Checking if GET /dashboard/admin returns 403 when accessed by a regular USER") void adminDashboard_AsRegularUser_ShouldReturnForbidden() throws Exception { - // Arrange - doThrow(new UnauthorizedException("Only administrators or system administrators can access this page.")) - .when(userService).validateAdmin(any(UserPrincipal.class)); - - // Act & Assert + // Act & Assert — @PreAuthorize("hasRole('ADMIN')") rejects USER mockMvc.perform(get("/dashboard/admin") .with(authentication(authFor(1L, "Test User", "user@test.com", UserAuthorization.USER)))) .andExpect(status().isForbidden()); @@ -386,11 +380,7 @@ void sysAdminDashboard_AsSysAdmin_ShouldReturnDashboardWithUsersAndLogs() throws @Test @DisplayName("Checking if GET /dashboard/sysadmin returns 403 when accessed by an ADMIN") void sysAdminDashboard_AsAdmin_ShouldReturnForbidden() throws Exception { - // Arrange - doThrow(new UnauthorizedException("Only system administrators can access this page.")) - .when(userService).validateSysAdmin(any(UserPrincipal.class)); - - // Act & Assert + // Act & Assert — @PreAuthorize("hasRole('SYSADMIN')") rejects ADMIN mockMvc.perform(get("/dashboard/sysadmin") .with(authentication(authFor(1L, "Test Admin", "admin@test.com", UserAuthorization.ADMIN)))) .andExpect(status().isForbidden()); @@ -399,11 +389,7 @@ void sysAdminDashboard_AsAdmin_ShouldReturnForbidden() throws Exception { @Test @DisplayName("Checking if GET /dashboard/sysadmin returns 403 when accessed by a regular USER") void sysAdminDashboard_AsRegularUser_ShouldReturnForbidden() throws Exception { - // Arrange - doThrow(new UnauthorizedException("Only system administrators can access this page.")) - .when(userService).validateSysAdmin(any(UserPrincipal.class)); - - // Act & Assert + // Act & Assert — @PreAuthorize("hasRole('SYSADMIN')") rejects USER mockMvc.perform(get("/dashboard/sysadmin") .with(authentication(authFor(1L, "Test User", "user@test.com", UserAuthorization.USER)))) .andExpect(status().isForbidden()); @@ -422,4 +408,4 @@ private Authentication authFor(Long id, String fullName, String email, UserAutho UserPrincipal principal = new UserPrincipal(user); return new UsernamePasswordAuthenticationToken(principal, null, principal.getAuthorities()); } -} +} \ No newline at end of file diff --git a/src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceIntegrationTest.java b/src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceIntegrationTest.java index 7145dd0..d7d5546 100644 --- a/src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceIntegrationTest.java +++ b/src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceIntegrationTest.java @@ -12,6 +12,7 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.ActiveProfiles; import org.springframework.transaction.annotation.Transactional; @@ -74,15 +75,15 @@ void updateUser_shouldUpdateUserFields_WhenDataIsValid() { } @Test + @WithMockUser(roles = "SYSADMIN") @DisplayName("Checking if updateUserAuthorization promotes a USER to ADMIN when requested by a SYSADMIN") void updateUserAuthorization_shouldChangeAuthorization_WhenRequestedBySysAdmin() { // Arrange - User sysAdmin = createAndSaveUser("SysAdmin", UserAuthorization.SYSADMIN); User targetUser = createAndSaveValidUser(); // Act UserDTO result = userService.updateUserAuthorization( - targetUser.getId(), UserAuthorization.ADMIN, sysAdmin.getId() + targetUser.getId(), UserAuthorization.ADMIN ); // Assert @@ -93,15 +94,15 @@ void updateUserAuthorization_shouldChangeAuthorization_WhenRequestedBySysAdmin() } @Test + @WithMockUser(roles = "SYSADMIN") @DisplayName("Checking if deleteUser removes the user row from the database when requested by a SYSADMIN") void deleteUser_shouldRemoveUser_WhenRequestedBySysAdmin() { // Arrange - User sysAdmin = createAndSaveUser("SysAdmin", UserAuthorization.SYSADMIN); User targetUser = createAndSaveValidUser(); Long targetId = targetUser.getId(); // Act - userService.deleteUser(targetId, sysAdmin.getId()); + userService.deleteUser(targetId); // Assert assertThat(userRepository.findById(targetId)).isEmpty(); @@ -186,39 +187,6 @@ void validateProfileAccess_shouldThrowUnauthorizedException_WhenUserAccessesOthe .isThrownBy(() -> userService.validateProfileAccess(principal, otherUserId)); } - @Test - @DisplayName("Checking if validateSysAdmin passes when principal has the SYSADMIN role") - void validateSysAdmin_shouldNotThrow_WhenPrincipalIsSysAdmin() { - // Arrange - User sysAdmin = createAndSaveUser("SysAdmin", UserAuthorization.SYSADMIN); - UserPrincipal principal = new UserPrincipal(sysAdmin); - - // Act & Assert — should complete without exception - userService.validateSysAdmin(principal); - } - - @Test - @DisplayName("Checking if validateAdmin passes when principal has the ADMIN role") - void validateAdmin_shouldNotThrow_WhenPrincipalIsAdmin() { - // Arrange - User admin = createAndSaveUser("Admin", UserAuthorization.ADMIN); - UserPrincipal principal = new UserPrincipal(admin); - - // Act & Assert — should complete without exception - userService.validateAdmin(principal); - } - - @Test - @DisplayName("Checking if validateAdmin also passes when principal has the SYSADMIN role") - void validateAdmin_shouldNotThrow_WhenPrincipalIsSysAdmin() { - // Arrange - User sysAdmin = createAndSaveUser("SysAdmin", UserAuthorization.SYSADMIN); - UserPrincipal principal = new UserPrincipal(sysAdmin); - - // Act & Assert — should complete without exception - userService.validateAdmin(principal); - } - // ── Helper methods ──────────────────────────────────────────────────────── private User createAndSaveValidUser() { diff --git a/src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceTest.java b/src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceTest.java index f5d5cb6..4258604 100644 --- a/src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceTest.java +++ b/src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceTest.java @@ -167,55 +167,25 @@ void updateUser_shouldUpdateAndReturnUser_WhenDataIsValid() { // ── updateUserAuthorization ─────────────────────────────────────────────── - @Test - @DisplayName("Checking if updateUserAuthorization throws UnauthorizedException when requester is not a SYSADMIN") - void updateUserAuthorization_shouldThrowUnauthorizedException_WhenRequesterIsNotSysAdmin() { - // Arrange - Long userId = 1L; - Long requesterId = 2L; - - User requester = new User(); - requester.setId(requesterId); - requester.setUserAuthorization(UserAuthorization.USER); - - when(userRepository.findById(requesterId)).thenReturn(Optional.of(requester)); - - // Act & Assert - assertThatThrownBy(() -> userService.updateUserAuthorization(userId, UserAuthorization.ADMIN, requesterId)) - .isInstanceOf(UnauthorizedException.class) - .hasMessageContaining("not authorized"); - } - @Test @DisplayName("Checking if updateUserAuthorization throws EntityNotFoundException when target user does not exist") void updateUserAuthorization_shouldThrowEntityNotFoundException_WhenTargetUserDoesNotExist() { // Arrange Long nonExistingUserId = 999L; - Long sysAdminId = 1L; - User sysAdmin = new User(); - sysAdmin.setId(sysAdminId); - sysAdmin.setUserAuthorization(UserAuthorization.SYSADMIN); - - when(userRepository.findById(sysAdminId)).thenReturn(Optional.of(sysAdmin)); when(userRepository.findById(nonExistingUserId)).thenReturn(Optional.empty()); // Act & Assert - assertThatThrownBy(() -> userService.updateUserAuthorization(nonExistingUserId, UserAuthorization.ADMIN, sysAdminId)) + assertThatThrownBy(() -> userService.updateUserAuthorization(nonExistingUserId, UserAuthorization.ADMIN)) .isInstanceOf(EntityNotFoundException.class) .hasMessage("User not found"); } @Test - @DisplayName("Checking if updateUserAuthorization changes role when requested by a SYSADMIN") - void updateUserAuthorization_shouldChangeAuthorization_WhenRequesterIsSysAdmin() { + @DisplayName("Checking if updateUserAuthorization changes role and returns updated UserDTO") + void updateUserAuthorization_shouldChangeAuthorization_WhenUserExists() { // Arrange Long userId = 1L; - Long sysAdminId = 2L; - - User sysAdmin = new User(); - sysAdmin.setId(sysAdminId); - sysAdmin.setUserAuthorization(UserAuthorization.SYSADMIN); User targetUser = new User(); targetUser.setId(userId); @@ -223,13 +193,12 @@ void updateUserAuthorization_shouldChangeAuthorization_WhenRequesterIsSysAdmin() UserDTO expectedDTO = new UserDTO(userId, "User", "user@test.com", UserAuthorization.ADMIN); - when(userRepository.findById(sysAdminId)).thenReturn(Optional.of(sysAdmin)); when(userRepository.findById(userId)).thenReturn(Optional.of(targetUser)); when(userRepository.save(any(User.class))).thenReturn(targetUser); when(userMapper.toDTO(targetUser)).thenReturn(expectedDTO); // Act - UserDTO result = userService.updateUserAuthorization(userId, UserAuthorization.ADMIN, sysAdminId); + UserDTO result = userService.updateUserAuthorization(userId, UserAuthorization.ADMIN); // Assert assertThat(result.userAuthorization()).isEqualTo(UserAuthorization.ADMIN); @@ -238,66 +207,33 @@ void updateUserAuthorization_shouldChangeAuthorization_WhenRequesterIsSysAdmin() // ── deleteUser ──────────────────────────────────────────────────────────── - @Test - @DisplayName("Checking if deleteUser throws UnauthorizedException when requester is not a SYSADMIN") - void deleteUser_shouldThrowUnauthorizedException_WhenRequesterIsNotSysAdmin() { - // Arrange - Long userId = 1L; - Long requesterId = 2L; - - User requester = new User(); - requester.setId(requesterId); - requester.setUserAuthorization(UserAuthorization.ADMIN); - - when(userRepository.findById(requesterId)).thenReturn(Optional.of(requester)); - - // Act & Assert - assertThatThrownBy(() -> userService.deleteUser(userId, requesterId)) - .isInstanceOf(UnauthorizedException.class) - .hasMessageContaining("not authorized"); - - verify(userRepository, never()).delete(any(User.class)); - } - @Test @DisplayName("Checking if deleteUser throws EntityNotFoundException when target user does not exist") void deleteUser_shouldThrowEntityNotFoundException_WhenTargetUserDoesNotExist() { // Arrange Long nonExistingUserId = 999L; - Long sysAdminId = 1L; - - User sysAdmin = new User(); - sysAdmin.setId(sysAdminId); - sysAdmin.setUserAuthorization(UserAuthorization.SYSADMIN); - when(userRepository.findById(sysAdminId)).thenReturn(Optional.of(sysAdmin)); when(userRepository.findById(nonExistingUserId)).thenReturn(Optional.empty()); // Act & Assert - assertThatThrownBy(() -> userService.deleteUser(nonExistingUserId, sysAdminId)) + assertThatThrownBy(() -> userService.deleteUser(nonExistingUserId)) .isInstanceOf(EntityNotFoundException.class) .hasMessage("User not found"); } @Test - @DisplayName("Checking if deleteUser removes the user when requested by a SYSADMIN") - void deleteUser_shouldDeleteUser_WhenRequesterIsSysAdmin() { + @DisplayName("Checking if deleteUser removes the user when the user exists") + void deleteUser_shouldDeleteUser_WhenUserExists() { // Arrange Long userId = 1L; - Long sysAdminId = 2L; - - User sysAdmin = new User(); - sysAdmin.setId(sysAdminId); - sysAdmin.setUserAuthorization(UserAuthorization.SYSADMIN); User targetUser = new User(); targetUser.setId(userId); - when(userRepository.findById(sysAdminId)).thenReturn(Optional.of(sysAdmin)); when(userRepository.findById(userId)).thenReturn(Optional.of(targetUser)); // Act - userService.deleteUser(userId, sysAdminId); + userService.deleteUser(userId); // Assert verify(userRepository, times(1)).delete(targetUser); @@ -326,48 +262,6 @@ void validateProfileAccess_shouldThrowUnauthorizedException_WhenUserAccessesOthe .hasMessageContaining("permission"); } - // ── validateSysAdmin (UserPrincipal) ────────────────────────────────────── - - @Test - @DisplayName("Checking if validateSysAdmin throws UnauthorizedException when principal is not a SYSADMIN") - void validateSysAdmin_shouldThrowUnauthorizedException_WhenPrincipalIsNotSysAdmin() { - // Arrange - User user = new User(); - user.setId(1L); - user.setFullName("Regular User"); - user.setEmail("user@test.com"); - user.setUsername("user@test.com"); - user.setPassword("password"); - user.setUserAuthorization(UserAuthorization.USER); - UserPrincipal principal = new UserPrincipal(user); - - // Act & Assert - assertThatThrownBy(() -> userService.validateSysAdmin(principal)) - .isInstanceOf(UnauthorizedException.class) - .hasMessageContaining("system administrators"); - } - - // ── validateAdmin ───────────────────────────────────────────────────────── - - @Test - @DisplayName("Checking if validateAdmin throws UnauthorizedException when principal is a regular USER") - void validateAdmin_shouldThrowUnauthorizedException_WhenPrincipalIsRegularUser() { - // Arrange - User user = new User(); - user.setId(1L); - user.setFullName("Regular User"); - user.setEmail("user@test.com"); - user.setUsername("user@test.com"); - user.setPassword("password"); - user.setUserAuthorization(UserAuthorization.USER); - UserPrincipal principal = new UserPrincipal(user); - - // Act & Assert - assertThatThrownBy(() -> userService.validateAdmin(principal)) - .isInstanceOf(UnauthorizedException.class) - .hasMessageContaining("administrators"); - } - // ── findById ────────────────────────────────────────────────────────────── @Test @@ -392,3 +286,4 @@ void findByEmail_shouldReturnEmpty_WhenEmailDoesNotExist() { assertThat(userService.findByEmail("nobody@test.com")).isEmpty(); } } + \ No newline at end of file From e9fe0fcfa4b1a24fa5611436dd9e3d589ca09c57 Mon Sep 17 00:00:00 2001 From: Martin Karlsson Date: Tue, 21 Apr 2026 10:56:32 +0200 Subject: [PATCH 5/7] Fix: - Added missing SecurityConfig import in UserViewControllerTest --- .../user/controller/UserViewControllerTest.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/test/java/org/example/visacasemanagementsystem/user/controller/UserViewControllerTest.java b/src/test/java/org/example/visacasemanagementsystem/user/controller/UserViewControllerTest.java index 5046b26..09f9740 100644 --- a/src/test/java/org/example/visacasemanagementsystem/user/controller/UserViewControllerTest.java +++ b/src/test/java/org/example/visacasemanagementsystem/user/controller/UserViewControllerTest.java @@ -1,6 +1,7 @@ package org.example.visacasemanagementsystem.user.controller; import org.example.visacasemanagementsystem.audit.service.AuditService; +import org.example.visacasemanagementsystem.config.SecurityConfig; import org.example.visacasemanagementsystem.exception.UnauthorizedException; import org.example.visacasemanagementsystem.user.UserAuthorization; import org.example.visacasemanagementsystem.user.dto.UserDTO; @@ -12,9 +13,10 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest; +import org.springframework.context.annotation.Import; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; @@ -32,13 +34,15 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @WebMvcTest(UserViewController.class) -@EnableMethodSecurity +@Import(SecurityConfig.class) @DisplayName("UserViewController web-layer tests") class UserViewControllerTest { @Autowired MockMvc mockMvc; + @MockitoBean + private UserDetailsService userDetailsService; @MockitoBean private UserService userService; @MockitoBean @@ -300,11 +304,13 @@ void userListView_AsSysAdmin_ShouldReturnListViewWithUsers() throws Exception { } @Test + @WithMockUser @DisplayName("Checking if GET /user/list returns 403 when accessed by a non-SYSADMIN") void userListView_AsNonSysAdmin_ShouldReturnForbidden() throws Exception { // Act & Assert — @PreAuthorize("hasRole('SYSADMIN')") rejects USER + mockMvc.perform(get("/user/list") - .with(authentication(authFor(1L, "Test User", "user@test.com", UserAuthorization.USER)))) + .with(authentication(authFor(1L, "Test User", "user@test.com", UserAuthorization.USER)))) .andExpect(status().isForbidden()); } @@ -397,6 +403,7 @@ void sysAdminDashboard_AsRegularUser_ShouldReturnForbidden() throws Exception { // ── Helper methods ──────────────────────────────────────────────────────── + private Authentication authFor(Long id, String fullName, String email, UserAuthorization auth) { User user = new User(); user.setId(id); From 5c07704de22d599e7ad3aea4ec9169b32bce9339 Mon Sep 17 00:00:00 2001 From: Martin Karlsson Date: Tue, 21 Apr 2026 12:04:47 +0200 Subject: [PATCH 6/7] Fix: - Removed @WithMockUser as per CodeRabbit suggestion - Strengthened test assertion as per CodeRabbit suggestion --- .../user/controller/UserViewControllerTest.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/test/java/org/example/visacasemanagementsystem/user/controller/UserViewControllerTest.java b/src/test/java/org/example/visacasemanagementsystem/user/controller/UserViewControllerTest.java index 09f9740..a33ec67 100644 --- a/src/test/java/org/example/visacasemanagementsystem/user/controller/UserViewControllerTest.java +++ b/src/test/java/org/example/visacasemanagementsystem/user/controller/UserViewControllerTest.java @@ -53,7 +53,6 @@ class UserViewControllerTest { // ── GET /user/signup ────────────────────────────────────────────────────── @Test - @WithMockUser @DisplayName("Checking if GET /user/signup returns the signup view") void userSignupForm_ShouldReturnSignupView() throws Exception { // Act & Assert @@ -65,7 +64,6 @@ void userSignupForm_ShouldReturnSignupView() throws Exception { // ── POST /user/signup ───────────────────────────────────────────────────── @Test - @WithMockUser @DisplayName("Checking if POST /user/signup with valid data redirects to the login page") void createUser_WithValidData_ShouldRedirectToLogin() throws Exception { // Arrange @@ -86,7 +84,6 @@ void createUser_WithValidData_ShouldRedirectToLogin() throws Exception { } @Test - @WithMockUser @DisplayName("Checking if POST /user/signup with a duplicate email re-renders the signup form with an error") void createUser_WithDuplicateEmail_ShouldReturnSignupViewWithError() throws Exception { // Arrange @@ -105,7 +102,6 @@ void createUser_WithDuplicateEmail_ShouldReturnSignupViewWithError() throws Exce } @Test - @WithMockUser @DisplayName("Checking if POST /user/signup with a short password re-renders the signup form with an error") void createUser_WithShortPassword_ShouldReturnSignupViewWithError() throws Exception { // Arrange @@ -126,7 +122,6 @@ void createUser_WithShortPassword_ShouldReturnSignupViewWithError() throws Excep // ── GET /user/login ─────────────────────────────────────────────────────── @Test - @WithMockUser @DisplayName("Checking if GET /user/login returns the login view") void userLoginForm_ShouldReturnLoginView() throws Exception { // Act & Assert From 03aea0c2ccc4824ad449c77dee16e3022701e3cf Mon Sep 17 00:00:00 2001 From: Martin Karlsson Date: Tue, 21 Apr 2026 12:05:17 +0200 Subject: [PATCH 7/7] Fix: - Strengthened test assertion as per CodeRabbit suggestion - File missed in last push --- .../user/service/UserServiceTest.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceTest.java b/src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceTest.java index 4258604..10e3620 100644 --- a/src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceTest.java +++ b/src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceTest.java @@ -85,7 +85,7 @@ void createUser_shouldThrowIllegalArgumentException_WhenEmailAlreadyExists() { void createUser_shouldSaveAndReturnUserDTO_WhenDataIsValid() { // Arrange CreateUserDTO dto = new CreateUserDTO( - "New User", "new@test.com", "password123", UserAuthorization.USER + "New User", "new@test.com", "password123", UserAuthorization.SYSADMIN ); User mappedUser = new User(); @@ -95,7 +95,7 @@ void createUser_shouldSaveAndReturnUserDTO_WhenDataIsValid() { when(userMapper.toEntity(dto)).thenReturn(mappedUser); when(passwordEncoder.encode("password123")).thenReturn("encodedPassword"); - when(userRepository.save(any(User.class))).thenReturn(savedUser); + when(userRepository.save(mappedUser)).thenReturn(savedUser); when(userMapper.toDTO(savedUser)).thenReturn(expectedDTO); // Act @@ -103,7 +103,10 @@ void createUser_shouldSaveAndReturnUserDTO_WhenDataIsValid() { // Assert assertThat(result).isEqualTo(expectedDTO); - verify(userRepository, times(1)).save(any(User.class)); + assertThat(mappedUser.getPassword()).isEqualTo("encodedPassword"); + assertThat(mappedUser.getUserAuthorization()).isEqualTo(UserAuthorization.USER); + verify(passwordEncoder).encode("password123"); + verify(userRepository).save(mappedUser); } // ── updateUser ──────────────────────────────────────────────────────────── @@ -201,6 +204,8 @@ void updateUserAuthorization_shouldChangeAuthorization_WhenUserExists() { UserDTO result = userService.updateUserAuthorization(userId, UserAuthorization.ADMIN); // Assert + assertThat(result).isEqualTo(expectedDTO); + assertThat(targetUser.getUserAuthorization()).isEqualTo(UserAuthorization.ADMIN); assertThat(result.userAuthorization()).isEqualTo(UserAuthorization.ADMIN); verify(userRepository).save(targetUser); }