From dbc76e615881a0413a98bc290c90308df26f7a26 Mon Sep 17 00:00:00 2001 From: Lobo Date: Sat, 4 Apr 2026 16:01:05 +0200 Subject: [PATCH] Added tests for all services and controller --- .../controller/LoginExceptionController.java | 16 ++-- .../controller/UserExceptionController.java | 14 +-- .../security/JwtServiceTest.java | 79 +++++++++++++++++ .../login/controller/LoginControllerTest.java | 77 +++++++++++++++++ .../login/service/LoginServiceTest.java | 85 +++++++++++++++++++ .../user/controller/UserControllerTest.java | 64 ++++++++++++++ .../domain/user/service/UserServiceTest.java | 53 ++++++++++++ 7 files changed, 367 insertions(+), 21 deletions(-) create mode 100644 src/test/java/auth_service/configuration/security/JwtServiceTest.java create mode 100644 src/test/java/auth_service/domain/login/controller/LoginControllerTest.java create mode 100644 src/test/java/auth_service/domain/login/service/LoginServiceTest.java create mode 100644 src/test/java/auth_service/domain/user/controller/UserControllerTest.java create mode 100644 src/test/java/auth_service/domain/user/service/UserServiceTest.java diff --git a/src/main/java/auth_service/domain/login/controller/LoginExceptionController.java b/src/main/java/auth_service/domain/login/controller/LoginExceptionController.java index d41eabc..734ce61 100644 --- a/src/main/java/auth_service/domain/login/controller/LoginExceptionController.java +++ b/src/main/java/auth_service/domain/login/controller/LoginExceptionController.java @@ -6,6 +6,7 @@ import auth_service.domain.login.exception.UserNotFoundException; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; import org.springframework.core.annotation.Order; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -14,15 +15,8 @@ import org.springframework.web.bind.annotation.RestControllerAdvice; @Order(value = 1) @Slf4j public class LoginExceptionController { - private static @NonNull ResponseEntity response500(ErrorResponse body) { - log.error(body.message(), body.code()); - return ResponseEntity.status(500) - .contentType(org.springframework.http.MediaType.APPLICATION_JSON) - .body(body); - } - - private static @NonNull ResponseEntity response400(ErrorResponse body) { - return ResponseEntity.status(400) + private static @NonNull ResponseEntity response(ErrorResponse body, HttpStatus status) { + return ResponseEntity.status(status.value()) .contentType(org.springframework.http.MediaType.APPLICATION_JSON) .body(body); } @@ -37,12 +31,12 @@ public class LoginExceptionController { @ExceptionHandler(UserNotFoundException.class) public ResponseEntity clientError(UserNotFoundException ex) { var body = new ErrorResponse("USER_NOT_FOUND", ex.getMessage()); - return response400(body); + return response(body, HttpStatus.NOT_ACCEPTABLE); } @ExceptionHandler(InvalidCredentialsException.class) public ResponseEntity clientError(InvalidCredentialsException ex) { var body = new ErrorResponse("INVALID_CREDENTIALS", ex.getMessage()); - return response400(body); + return response(body, HttpStatus.UNAUTHORIZED); } } \ No newline at end of file diff --git a/src/main/java/auth_service/domain/user/controller/UserExceptionController.java b/src/main/java/auth_service/domain/user/controller/UserExceptionController.java index 3cece3a..5e9a800 100644 --- a/src/main/java/auth_service/domain/user/controller/UserExceptionController.java +++ b/src/main/java/auth_service/domain/user/controller/UserExceptionController.java @@ -5,6 +5,7 @@ import auth_service.domain.user.exception.UserNotFoundException; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; import org.springframework.core.annotation.Order; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -13,15 +14,8 @@ import org.springframework.web.bind.annotation.RestControllerAdvice; @Order(value = 1) @Slf4j public class UserExceptionController { - private static @NonNull ResponseEntity response500(ErrorResponse body) { - log.error(body.message(), body.code()); - return ResponseEntity.status(500) - .contentType(org.springframework.http.MediaType.APPLICATION_JSON) - .body(body); - } - - private static @NonNull ResponseEntity response400(ErrorResponse body) { - return ResponseEntity.status(400) + private static @NonNull ResponseEntity response(ErrorResponse body, HttpStatus status) { + return ResponseEntity.status(status.value()) .contentType(org.springframework.http.MediaType.APPLICATION_JSON) .body(body); } @@ -36,7 +30,7 @@ public class UserExceptionController { @ExceptionHandler(UserNotFoundException.class) public ResponseEntity clientError(UserNotFoundException ex) { var body = new ErrorResponse("USER_NOT_FOUND", ex.getMessage()); - return response400(body); + return response(body, HttpStatus.NOT_FOUND); } } \ No newline at end of file diff --git a/src/test/java/auth_service/configuration/security/JwtServiceTest.java b/src/test/java/auth_service/configuration/security/JwtServiceTest.java new file mode 100644 index 0000000..2f1d687 --- /dev/null +++ b/src/test/java/auth_service/configuration/security/JwtServiceTest.java @@ -0,0 +1,79 @@ +package auth_service.configuration.security; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.when; + +/** + * Tests for JwtService. + */ +@ExtendWith(MockitoExtension.class) +class JwtServiceTest { + + @Mock + private JwtConfig jwtConfig; + + private JwtService jwtService; + + @BeforeEach + void setUp() { + when(jwtConfig.getSecret()).thenReturn("verysecretkeythatmustbeatleast32characterslongforhmacsha"); + jwtService = new JwtService(jwtConfig); + } + + @Test + void generateToken_Success() { + // Arrange + var email = "test@example.com"; + + // Act + var token = jwtService.generateToken(email); + + // Assert + assertNotNull(token); + assertFalse(token.isEmpty()); + } + + @Test + void extractEmail_Success() { + // Arrange + var email = "test@example.com"; + var token = jwtService.generateToken(email); + + // Act + var extractedEmail = jwtService.extractEmail(token); + + // Assert + assertEquals(email, extractedEmail); + } + + @Test + void isTokenValid_Success() { + // Arrange + var email = "test@example.com"; + var token = jwtService.generateToken(email); + + // Act + var isValid = jwtService.isTokenValid(token); + + // Assert + assertTrue(isValid); + } + + @Test + void isTokenValid_Failure() { + // Arrange + var invalidToken = "invalid-token"; + + // Act + var isValid = jwtService.isTokenValid(invalidToken); + + // Assert + assertFalse(isValid); + } +} \ No newline at end of file diff --git a/src/test/java/auth_service/domain/login/controller/LoginControllerTest.java b/src/test/java/auth_service/domain/login/controller/LoginControllerTest.java new file mode 100644 index 0000000..e078a0f --- /dev/null +++ b/src/test/java/auth_service/domain/login/controller/LoginControllerTest.java @@ -0,0 +1,77 @@ +package auth_service.domain.login.controller; + +import auth_service.configuration.security.JwtAuthenticationFilter; +import auth_service.configuration.security.JwtService; +import auth_service.domain.login.controller.dto.request.LoginRequest; +import auth_service.domain.login.exception.InvalidCredentialsException; +import auth_service.domain.login.model.User; +import auth_service.domain.login.service.LoginService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.json.AutoConfigureJsonTesters; +import org.springframework.boot.test.json.JacksonTester; +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; +import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * Tests for LoginController. + */ +@WebMvcTest(LoginController.class) +@AutoConfigureMockMvc(addFilters = false) // Skip security filters for simple controller test +@AutoConfigureJsonTesters +class LoginControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockitoBean + private LoginService loginService; + + @MockitoBean + private JwtService jwtService; + + @MockitoBean + private JwtAuthenticationFilter jwtAuthenticationFilter; // Needed because it's used in SecurityConfig + + @Autowired + private JacksonTester loginRequestJson; + + @Test + void login_Success() throws Exception { + // Arrange + var request = new LoginRequest("test@example.com", "password"); + var authenticatedUser = User.builder().email("test@example.com").build(); + when(loginService.login(any(User.class))).thenReturn(authenticatedUser); + when(jwtService.generateToken("test@example.com")).thenReturn("mock-token"); + + // Act & Assert + mockMvc.perform(post("/api/auth/login") + .contentType(MediaType.APPLICATION_JSON) + .content(loginRequestJson.write(request).getJson())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.message").value("Login successful")) + .andExpect(jsonPath("$.token").value("mock-token")); + } + + @Test + void login_Failure() throws Exception { + // Arrange + var request = new LoginRequest("test@example.com", "wrong-password"); + when(loginService.login(any(User.class))).thenThrow(new InvalidCredentialsException("Invalid credentials")); + + // Act & Assert + mockMvc.perform(post("/api/auth/login") + .contentType(MediaType.APPLICATION_JSON) + .content(loginRequestJson.write(request).getJson())) + .andExpect(status().isUnauthorized()); + } +} \ No newline at end of file diff --git a/src/test/java/auth_service/domain/login/service/LoginServiceTest.java b/src/test/java/auth_service/domain/login/service/LoginServiceTest.java new file mode 100644 index 0000000..06fd225 --- /dev/null +++ b/src/test/java/auth_service/domain/login/service/LoginServiceTest.java @@ -0,0 +1,85 @@ +package auth_service.domain.login.service; + +import auth_service.domain.login.exception.InvalidCredentialsException; +import auth_service.domain.login.exception.UserNotFoundException; +import auth_service.domain.login.model.User; +import auth_service.domain.login.persistence.MinimalUserRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.crypto.password.PasswordEncoder; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.when; + +/** + * Tests for LoginService. + */ +@ExtendWith(MockitoExtension.class) +class LoginServiceTest { + + @Mock + private MinimalUserRepository minimalUserRepository; + + @Mock + private PasswordEncoder passwordEncoder; + + @InjectMocks + private LoginService loginService; + + private User user; + + @BeforeEach + void setUp() { + user = User.builder() + .email("test@example.com") + .password("password") + .build(); + } + + @Test + void login_Success() { + // Arrange + var dbUser = User.builder() + .email("test@example.com") + .password("encodedPassword") + .build(); + when(minimalUserRepository.findByEmail(user.getEmail())).thenReturn(Optional.of(dbUser)); + when(passwordEncoder.matches(user.getPassword(), dbUser.getPassword())).thenReturn(true); + + // Act + var result = loginService.login(user); + + // Assert + assertEquals(dbUser, result); + } + + @Test + void login_UserNotFound() { + // Arrange + when(minimalUserRepository.findByEmail(user.getEmail())).thenReturn(Optional.empty()); + + // Act & Assert + assertThrows(UserNotFoundException.class, () -> loginService.login(user)); + } + + @Test + void login_InvalidCredentials() { + // Arrange + var dbUser = User.builder() + .email("test@example.com") + .password("encodedPassword") + .build(); + when(minimalUserRepository.findByEmail(user.getEmail())).thenReturn(Optional.of(dbUser)); + when(passwordEncoder.matches(user.getPassword(), dbUser.getPassword())).thenReturn(false); + + // Act & Assert + assertThrows(InvalidCredentialsException.class, () -> loginService.login(user)); + } +} \ No newline at end of file diff --git a/src/test/java/auth_service/domain/user/controller/UserControllerTest.java b/src/test/java/auth_service/domain/user/controller/UserControllerTest.java new file mode 100644 index 0000000..116ca50 --- /dev/null +++ b/src/test/java/auth_service/domain/user/controller/UserControllerTest.java @@ -0,0 +1,64 @@ +package auth_service.domain.user.controller; + +import auth_service.configuration.security.JwtAuthenticationFilter; +import auth_service.configuration.security.JwtService; +import auth_service.domain.user.exception.UserNotFoundException; +import auth_service.domain.user.model.User; +import auth_service.domain.user.service.UserService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; +import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; + +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(UserController.class) +@AutoConfigureMockMvc(addFilters = false) +class UserControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockitoBean + private UserService userService; + + @MockitoBean + private JwtService jwtService; + + @MockitoBean + private JwtAuthenticationFilter jwtAuthenticationFilter; + + @Test + void getCurrentUserV1_Success() throws Exception { + // Arrange + var email = "test@example.com"; + var user = User.builder().email(email).build(); + when(userService.getUserByEmail(email)).thenReturn(user); + + // Act & Assert + mockMvc.perform(get("/api/users/me") + .principal(() -> email) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.email").value(email)); + } + + @Test + void getCurrentUserV1_NotFound() throws Exception { + // Arrange + var email = "notfound@example.com"; + when(userService.getUserByEmail(email)).thenThrow(new UserNotFoundException("User not found")); + + // Act & Assert + mockMvc.perform(get("/api/users/me") + .principal(() -> email) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } +} \ No newline at end of file diff --git a/src/test/java/auth_service/domain/user/service/UserServiceTest.java b/src/test/java/auth_service/domain/user/service/UserServiceTest.java new file mode 100644 index 0000000..c817f84 --- /dev/null +++ b/src/test/java/auth_service/domain/user/service/UserServiceTest.java @@ -0,0 +1,53 @@ +package auth_service.domain.user.service; + +import auth_service.domain.user.exception.UserNotFoundException; +import auth_service.domain.user.model.User; +import auth_service.domain.user.persistence.UserRepository; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.when; + +/** + * Tests for UserService. + */ +@ExtendWith(MockitoExtension.class) +class UserServiceTest { + + @Mock + private UserRepository userRepository; + + @InjectMocks + private UserService userService; + + @Test + void getUserByEmail_Success() { + // Arrange + var email = "test@example.com"; + var user = User.builder().email(email).build(); + when(userRepository.findByEmail(email)).thenReturn(Optional.of(user)); + + // Act + var result = userService.getUserByEmail(email); + + // Assert + assertEquals(user, result); + } + + @Test + void getUserByEmail_NotFound() { + // Arrange + var email = "notfound@example.com"; + when(userRepository.findByEmail(email)).thenReturn(Optional.empty()); + + // Act & Assert + assertThrows(UserNotFoundException.class, () -> userService.getUserByEmail(email)); + } +} \ No newline at end of file