From 1c0fec1b57c496c172e2d161042e159bbb94a292 Mon Sep 17 00:00:00 2001 From: "DESKTOP-E7L6HLO\\Noyan" Date: Sun, 21 Jul 2024 00:20:33 +0300 Subject: [PATCH] Task 55 : Implement JUnit tests for auth service and user service --- authservice/pom.xml | 6 + .../model/auth/dto/request/LoginRequest.java | 6 +- .../base/AbstractBaseServiceTest.java | 12 + .../base/AbstractRestControllerTest.java | 19 ++ .../controller/AuthControllerTest.java | 153 +++++++++ .../exception/CustomErrorTest.java | 96 ++++++ .../authservice/model/auth/TokenTest.java | 131 ++++++++ .../authservice/model/auth/UserTest.java | 119 +++++++ .../auth/dto/response/TokenResponseTest.java | 86 +++++ .../CustomAuthenticationEntryPointTest.java | 120 +++++++ .../service/impl/LogoutServiceImplTest.java | 84 +++++ .../impl/RefreshTokenServiceImplTest.java | 91 ++++++ .../service/impl/RegisterServiceImplTest.java | 95 ++++++ .../impl/UserLoginServiceImplTest.java | 83 +++++ .../handler/GlobalExceptionHandler.java | 4 +- .../service/impl/TokenServiceImpl.java | 3 + .../controller/UserControllerTest.java | 52 ++- .../PasswordNotValidExceptionTest.java | 39 +++ .../TokenAlreadyInvalidatedExceptionTest.java | 42 +++ .../UserAlreadyExistExceptionTest.java | 42 +++ .../exception/UserNotFoundExceptionTest.java | 42 +++ .../UserStatusNotValidExceptionTest.java | 42 +++ .../handler/GlobalExceptionHandlerTest.java | 265 +++++++++++++++ .../CustomAuthenticationEntryPointTest.java | 122 +++++++ .../impl/InvalidTokenServiceImplTest.java | 87 +++++ .../service/impl/TokenServiceImplTest.java | 305 ++++++++++++++++++ .../userservice/utils/KeyConverterTest.java | 62 ++++ 27 files changed, 2199 insertions(+), 9 deletions(-) create mode 100644 authservice/src/test/java/com/springbootmicroservices/authservice/base/AbstractBaseServiceTest.java create mode 100644 authservice/src/test/java/com/springbootmicroservices/authservice/base/AbstractRestControllerTest.java create mode 100644 authservice/src/test/java/com/springbootmicroservices/authservice/controller/AuthControllerTest.java create mode 100644 authservice/src/test/java/com/springbootmicroservices/authservice/exception/CustomErrorTest.java create mode 100644 authservice/src/test/java/com/springbootmicroservices/authservice/model/auth/TokenTest.java create mode 100644 authservice/src/test/java/com/springbootmicroservices/authservice/model/auth/UserTest.java create mode 100644 authservice/src/test/java/com/springbootmicroservices/authservice/model/auth/dto/response/TokenResponseTest.java create mode 100644 authservice/src/test/java/com/springbootmicroservices/authservice/security/CustomAuthenticationEntryPointTest.java create mode 100644 authservice/src/test/java/com/springbootmicroservices/authservice/service/impl/LogoutServiceImplTest.java create mode 100644 authservice/src/test/java/com/springbootmicroservices/authservice/service/impl/RefreshTokenServiceImplTest.java create mode 100644 authservice/src/test/java/com/springbootmicroservices/authservice/service/impl/RegisterServiceImplTest.java create mode 100644 authservice/src/test/java/com/springbootmicroservices/authservice/service/impl/UserLoginServiceImplTest.java create mode 100644 userservice/src/test/java/com/springbootmicroservices/userservice/exception/PasswordNotValidExceptionTest.java create mode 100644 userservice/src/test/java/com/springbootmicroservices/userservice/exception/TokenAlreadyInvalidatedExceptionTest.java create mode 100644 userservice/src/test/java/com/springbootmicroservices/userservice/exception/UserAlreadyExistExceptionTest.java create mode 100644 userservice/src/test/java/com/springbootmicroservices/userservice/exception/UserNotFoundExceptionTest.java create mode 100644 userservice/src/test/java/com/springbootmicroservices/userservice/exception/UserStatusNotValidExceptionTest.java create mode 100644 userservice/src/test/java/com/springbootmicroservices/userservice/exception/handler/GlobalExceptionHandlerTest.java create mode 100644 userservice/src/test/java/com/springbootmicroservices/userservice/security/CustomAuthenticationEntryPointTest.java create mode 100644 userservice/src/test/java/com/springbootmicroservices/userservice/service/impl/InvalidTokenServiceImplTest.java create mode 100644 userservice/src/test/java/com/springbootmicroservices/userservice/service/impl/TokenServiceImplTest.java create mode 100644 userservice/src/test/java/com/springbootmicroservices/userservice/utils/KeyConverterTest.java diff --git a/authservice/pom.xml b/authservice/pom.xml index 65b34ac..9a8c193 100644 --- a/authservice/pom.xml +++ b/authservice/pom.xml @@ -135,6 +135,12 @@ spring-cloud-starter-openfeign + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + diff --git a/authservice/src/main/java/com/springbootmicroservices/authservice/model/auth/dto/request/LoginRequest.java b/authservice/src/main/java/com/springbootmicroservices/authservice/model/auth/dto/request/LoginRequest.java index 93e2aaf..788e272 100644 --- a/authservice/src/main/java/com/springbootmicroservices/authservice/model/auth/dto/request/LoginRequest.java +++ b/authservice/src/main/java/com/springbootmicroservices/authservice/model/auth/dto/request/LoginRequest.java @@ -1,13 +1,13 @@ package com.springbootmicroservices.authservice.model.auth.dto.request; import jakarta.validation.constraints.NotBlank; -import lombok.Builder; -import lombok.Getter; -import lombok.Setter; +import lombok.*; @Getter @Setter @Builder +@NoArgsConstructor +@AllArgsConstructor public class LoginRequest { @NotBlank diff --git a/authservice/src/test/java/com/springbootmicroservices/authservice/base/AbstractBaseServiceTest.java b/authservice/src/test/java/com/springbootmicroservices/authservice/base/AbstractBaseServiceTest.java new file mode 100644 index 0000000..bf881bd --- /dev/null +++ b/authservice/src/test/java/com/springbootmicroservices/authservice/base/AbstractBaseServiceTest.java @@ -0,0 +1,12 @@ +package com.springbootmicroservices.authservice.base; + +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +public abstract class AbstractBaseServiceTest { + +} diff --git a/authservice/src/test/java/com/springbootmicroservices/authservice/base/AbstractRestControllerTest.java b/authservice/src/test/java/com/springbootmicroservices/authservice/base/AbstractRestControllerTest.java new file mode 100644 index 0000000..9a4def6 --- /dev/null +++ b/authservice/src/test/java/com/springbootmicroservices/authservice/base/AbstractRestControllerTest.java @@ -0,0 +1,19 @@ +package com.springbootmicroservices.authservice.base; + +import com.fasterxml.jackson.databind.ObjectMapper; +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.test.web.servlet.MockMvc; + +@SpringBootTest +@AutoConfigureMockMvc +public class AbstractRestControllerTest { + + @Autowired + protected MockMvc mockMvc; + + @Autowired + protected ObjectMapper objectMapper; + +} diff --git a/authservice/src/test/java/com/springbootmicroservices/authservice/controller/AuthControllerTest.java b/authservice/src/test/java/com/springbootmicroservices/authservice/controller/AuthControllerTest.java new file mode 100644 index 0000000..371ded5 --- /dev/null +++ b/authservice/src/test/java/com/springbootmicroservices/authservice/controller/AuthControllerTest.java @@ -0,0 +1,153 @@ +package com.springbootmicroservices.authservice.controller; + +import com.springbootmicroservices.authservice.base.AbstractRestControllerTest; +import com.springbootmicroservices.authservice.model.auth.dto.request.LoginRequest; +import com.springbootmicroservices.authservice.model.auth.dto.request.RegisterRequest; +import com.springbootmicroservices.authservice.model.auth.dto.request.TokenInvalidateRequest; +import com.springbootmicroservices.authservice.model.auth.dto.request.TokenRefreshRequest; +import com.springbootmicroservices.authservice.model.auth.dto.response.TokenResponse; +import com.springbootmicroservices.authservice.model.common.dto.response.CustomResponse; +import com.springbootmicroservices.authservice.service.LogoutService; +import com.springbootmicroservices.authservice.service.RefreshTokenService; +import com.springbootmicroservices.authservice.service.RegisterService; +import com.springbootmicroservices.authservice.service.UserLoginService; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; +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; + +class AuthControllerTest extends AbstractRestControllerTest { + + @MockBean + private RegisterService registerService; + + @MockBean + private UserLoginService userLoginService; + + @MockBean + private RefreshTokenService refreshTokenService; + + @MockBean + private LogoutService logoutService; + + @Test + void registerAdmin_ValidRequest_ReturnsSuccess() throws Exception { + + // Given + RegisterRequest registerRequest = RegisterRequest.builder() + .email("valid.email@example.com") + .password("validPassword123") + .firstName("John") + .lastName("Doe") + .phoneNumber("1234567890100") + .role("user") + .build(); + + // When & Then + mockMvc.perform(post("/api/v1/authentication/users/register") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(registerRequest))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.isSuccess").value(true)) + .andExpect(jsonPath("$.httpStatus").value("OK")); + + // Verify + verify(registerService, times(1)).registerUser(any(RegisterRequest.class)); + + } + + @Test + void loginUser_ValidRequest_ReturnsTokenResponse() throws Exception { + + // Given + LoginRequest loginRequest = LoginRequest.builder() + .email("valid.email@example.com") + .password("validPassword123") + .build(); + + TokenResponse tokenResponse = TokenResponse.builder() + .accessToken("newAccessToken") + .accessTokenExpiresAt(System.currentTimeMillis() + 3600) + .refreshToken("newRefreshToken") + .build(); + + CustomResponse expectedResponse = CustomResponse.successOf(tokenResponse); + + // When + when(userLoginService.login(any(LoginRequest.class))).thenReturn(expectedResponse); + + // Then + mockMvc.perform(post("/api/v1/authentication/users/login") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(loginRequest))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.isSuccess").value(true)) + .andExpect(jsonPath("$.httpStatus").value("OK")) + .andExpect(jsonPath("$.response.accessToken").value("newAccessToken")); + + // Verify + verify(userLoginService, times(1)).login(any(LoginRequest.class)); + + } + + @Test + void refreshToken_ValidRequest_ReturnsTokenResponse() throws Exception { + + // Given + TokenRefreshRequest tokenRefreshRequest = TokenRefreshRequest.builder() + .refreshToken("validRefreshToken") + .build(); + + TokenResponse tokenResponse = TokenResponse.builder() + .accessToken("newAccessToken") + .accessTokenExpiresAt(System.currentTimeMillis() + 3600) + .refreshToken("newRefreshToken") + .build(); + + CustomResponse expectedResponse = CustomResponse.successOf(tokenResponse); + + // When + when(refreshTokenService.refreshToken(any(TokenRefreshRequest.class))).thenReturn(expectedResponse); + + // Then + mockMvc.perform(post("/api/v1/authentication/users/refresh-token") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(tokenRefreshRequest))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.isSuccess").value(true)) + .andExpect(jsonPath("$.httpStatus").value("OK")) + .andExpect(jsonPath("$.response.accessToken").value("newAccessToken")); + + verify(refreshTokenService, times(1)).refreshToken(any(TokenRefreshRequest.class)); + } + + @Test + void logout_ValidRequest_ReturnsSuccess() throws Exception { + + // Given + TokenInvalidateRequest tokenInvalidateRequest = TokenInvalidateRequest.builder() + .accessToken("validAccessToken") + .refreshToken("validRefreshToken") + .build(); + + // When & Then + mockMvc.perform(post("/api/v1/authentication/users/logout") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(tokenInvalidateRequest))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.isSuccess").value(true)) + .andExpect(jsonPath("$.httpStatus").value("OK")); + + // Verify + verify(logoutService, times(1)).logout(any(TokenInvalidateRequest.class)); + + } + + +} \ No newline at end of file diff --git a/authservice/src/test/java/com/springbootmicroservices/authservice/exception/CustomErrorTest.java b/authservice/src/test/java/com/springbootmicroservices/authservice/exception/CustomErrorTest.java new file mode 100644 index 0000000..eab2093 --- /dev/null +++ b/authservice/src/test/java/com/springbootmicroservices/authservice/exception/CustomErrorTest.java @@ -0,0 +1,96 @@ +package com.springbootmicroservices.authservice.exception; + +import static org.junit.jupiter.api.Assertions.*; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpStatus; + +import java.time.LocalDateTime; +import java.util.Collections; + +public class CustomErrorTest { + + @Test + void testCustomErrorBuilder_WithAllFields() { + + LocalDateTime now = LocalDateTime.now(); + CustomError.CustomSubError subError = CustomError.CustomSubError.builder() + .message("Sub error message") + .field("field") + .value("value") + .type("type") + .build(); + + CustomError customError = CustomError.builder() + .time(now) + .httpStatus(HttpStatus.BAD_REQUEST) + .header(CustomError.Header.VALIDATION_ERROR.getName()) + .message("Main error message") + .subErrors(Collections.singletonList(subError)) + .build(); + + assertNotNull(customError); + assertEquals(now, customError.getTime()); + assertEquals(HttpStatus.BAD_REQUEST, customError.getHttpStatus()); + assertEquals(CustomError.Header.VALIDATION_ERROR.getName(), customError.getHeader()); + assertEquals("Main error message", customError.getMessage()); + assertFalse(customError.getIsSuccess()); + assertNotNull(customError.getSubErrors()); + assertEquals(1, customError.getSubErrors().size()); + assertEquals(subError, customError.getSubErrors().get(0)); + } + + @Test + void testCustomErrorBuilder_DefaultValues() { + CustomError customError = CustomError.builder().build(); + + assertNotNull(customError); + assertNotNull(customError.getTime()); + assertEquals(false, customError.getIsSuccess()); + assertNull(customError.getHttpStatus()); + assertNull(customError.getHeader()); + assertNull(customError.getMessage()); + assertNull(customError.getSubErrors()); + } + + @Test + void testCustomSubErrorBuilder_WithAllFields() { + CustomError.CustomSubError subError = CustomError.CustomSubError.builder() + .message("Sub error message") + .field("field") + .value("value") + .type("type") + .build(); + + assertNotNull(subError); + assertEquals("Sub error message", subError.getMessage()); + assertEquals("field", subError.getField()); + assertEquals("value", subError.getValue()); + assertEquals("type", subError.getType()); + } + + @Test + void testCustomSubErrorBuilder_DefaultValues() { + CustomError.CustomSubError subError = CustomError.CustomSubError.builder().build(); + + assertNotNull(subError); + assertNull(subError.getMessage()); + assertNull(subError.getField()); + assertNull(subError.getValue()); + assertNull(subError.getType()); + } + + @Test + void testCustomErrorHeaderEnum() { + assertEquals("API ERROR", CustomError.Header.API_ERROR.getName()); + assertEquals("ALREADY EXIST", CustomError.Header.ALREADY_EXIST.getName()); + assertEquals("NOT EXIST", CustomError.Header.NOT_FOUND.getName()); + assertEquals("VALIDATION ERROR", CustomError.Header.VALIDATION_ERROR.getName()); + assertEquals("DATABASE ERROR", CustomError.Header.DATABASE_ERROR.getName()); + assertEquals("PROCESS ERROR", CustomError.Header.PROCESS_ERROR.getName()); + assertEquals("AUTH ERROR", CustomError.Header.AUTH_ERROR.getName()); + } + +} \ No newline at end of file diff --git a/authservice/src/test/java/com/springbootmicroservices/authservice/model/auth/TokenTest.java b/authservice/src/test/java/com/springbootmicroservices/authservice/model/auth/TokenTest.java new file mode 100644 index 0000000..686e4ec --- /dev/null +++ b/authservice/src/test/java/com/springbootmicroservices/authservice/model/auth/TokenTest.java @@ -0,0 +1,131 @@ +package com.springbootmicroservices.authservice.model.auth; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.springframework.util.StringUtils; + +public class TokenTest { + + @Test + void testTokenBuilder_WithAllFields() { + + // Given + String accessToken = "sampleAccessToken"; + Long accessTokenExpiresAt = System.currentTimeMillis() + 3600; + String refreshToken = "sampleRefreshToken"; + + // When + Token token = Token.builder() + .accessToken(accessToken) + .accessTokenExpiresAt(accessTokenExpiresAt) + .refreshToken(refreshToken) + .build(); + + // Then + assertNotNull(token); + assertEquals(accessToken, token.getAccessToken()); + assertEquals(accessTokenExpiresAt, token.getAccessTokenExpiresAt()); + assertEquals(refreshToken, token.getRefreshToken()); + + } + + @Test + void testTokenBuilder_DefaultValues() { + + // When + Token token = Token.builder().build(); + + // Then + assertNotNull(token); + assertNull(token.getAccessToken()); + assertNull(token.getAccessTokenExpiresAt()); + assertNull(token.getRefreshToken()); + + } + + @Test + void testIsBearerToken_WithValidBearerToken() { + + // Given + String authorizationHeader = "Bearer sampleAccessToken"; + + // When + boolean result = Token.isBearerToken(authorizationHeader); + + // Then + assertTrue(result); + + } + + @Test + void testIsBearerToken_WithInvalidBearerToken() { + + // Given + String authorizationHeader = "sampleAccessToken"; + + // When + boolean result = Token.isBearerToken(authorizationHeader); + + // Then + assertFalse(result); + + } + + @Test + void testIsBearerToken_WithEmptyHeader() { + + // Given + String authorizationHeader = ""; + + // When + boolean result = Token.isBearerToken(authorizationHeader); + + // Then + assertFalse(result); + + } + + @Test + void testGetJwt_WithBearerToken() { + + // Given + String authorizationHeader = "Bearer sampleAccessToken"; + + // When + String jwt = Token.getJwt(authorizationHeader); + + // Then + assertEquals("sampleAccessToken", jwt); + + } + + @Test + void testGetJwt_WithInvalidTokenFormat() { + + // Given + String authorizationHeader = "sampleAccessToken"; + + // When + String jwt = Token.getJwt(authorizationHeader); + + // Then + assertEquals("sampleAccessToken", jwt); + + } + + @Test + void testGetJwt_WithEmptyHeader() { + + // Given + String authorizationHeader = ""; + + // When + String jwt = Token.getJwt(authorizationHeader); + + // Then + assertEquals("", jwt); + + } + +} diff --git a/authservice/src/test/java/com/springbootmicroservices/authservice/model/auth/UserTest.java b/authservice/src/test/java/com/springbootmicroservices/authservice/model/auth/UserTest.java new file mode 100644 index 0000000..6d5ae47 --- /dev/null +++ b/authservice/src/test/java/com/springbootmicroservices/authservice/model/auth/UserTest.java @@ -0,0 +1,119 @@ +package com.springbootmicroservices.authservice.model.auth; + +import static org.junit.jupiter.api.Assertions.*; + +import com.springbootmicroservices.authservice.model.auth.enums.UserStatus; +import com.springbootmicroservices.authservice.model.auth.enums.UserType; +import org.junit.jupiter.api.Test; + +public class UserTest { + + @Test + void testUserBuilder_WithAllFields() { + + // Given + String id = "12345"; + String email = "example@example.com"; + String firstName = "John"; + String lastName = "Doe"; + String phoneNumber = "1234567890"; + UserStatus userStatus = UserStatus.ACTIVE; + UserType userType = UserType.ADMIN; + + // When + User user = User.builder() + .id(id) + .email(email) + .firstName(firstName) + .lastName(lastName) + .phoneNumber(phoneNumber) + .userStatus(userStatus) + .userType(userType) + .build(); + + // Then + assertNotNull(user); + assertEquals(id, user.getId()); + assertEquals(email, user.getEmail()); + assertEquals(firstName, user.getFirstName()); + assertEquals(lastName, user.getLastName()); + assertEquals(phoneNumber, user.getPhoneNumber()); + assertEquals(userStatus, user.getUserStatus()); + assertEquals(userType, user.getUserType()); + + } + + @Test + void testUserBuilder_DefaultValues() { + + // When + User user = User.builder().build(); + + // Then + assertNotNull(user); + assertNull(user.getId()); + assertNull(user.getEmail()); + assertNull(user.getFirstName()); + assertNull(user.getLastName()); + assertNull(user.getPhoneNumber()); + assertNull(user.getUserStatus()); + assertNull(user.getUserType()); + + } + + @Test + void testUserSettersAndGetters() { + + // Given + User user = new User(); + + // When + user.setId("12345"); + user.setEmail("example@example.com"); + user.setFirstName("John"); + user.setLastName("Doe"); + user.setPhoneNumber("1234567890"); + user.setUserStatus(UserStatus.ACTIVE); + user.setUserType(UserType.ADMIN); + + // Then + assertEquals("12345", user.getId()); + assertEquals("example@example.com", user.getEmail()); + assertEquals("John", user.getFirstName()); + assertEquals("Doe", user.getLastName()); + assertEquals("1234567890", user.getPhoneNumber()); + assertEquals(UserStatus.ACTIVE, user.getUserStatus()); + assertEquals(UserType.ADMIN, user.getUserType()); + + } + + @Test + void testUserEquality() { + + // Given + User user1 = User.builder() + .id("12345") + .email("example@example.com") + .firstName("John") + .lastName("Doe") + .phoneNumber("1234567890") + .userStatus(UserStatus.ACTIVE) + .userType(UserType.ADMIN) + .build(); + + User user3 = User.builder() + .id("67890") + .email("different@example.com") + .firstName("Jane") + .lastName("Smith") + .phoneNumber("0987654321") + .userStatus(UserStatus.ACTIVE) + .userType(UserType.USER) + .build(); + + // When & Then + assertNotEquals(user1, user3); + + } + +} diff --git a/authservice/src/test/java/com/springbootmicroservices/authservice/model/auth/dto/response/TokenResponseTest.java b/authservice/src/test/java/com/springbootmicroservices/authservice/model/auth/dto/response/TokenResponseTest.java new file mode 100644 index 0000000..5bdeabb --- /dev/null +++ b/authservice/src/test/java/com/springbootmicroservices/authservice/model/auth/dto/response/TokenResponseTest.java @@ -0,0 +1,86 @@ +package com.springbootmicroservices.authservice.model.auth.dto.response; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class TokenResponseTest { + + @Test + void testTokenResponseBuilder_WithAllFields() { + + // Given + String accessToken = "sampleAccessToken"; + Long accessTokenExpiresAt = System.currentTimeMillis() + 3600; + String refreshToken = "sampleRefreshToken"; + + // When + TokenResponse tokenResponse = TokenResponse.builder() + .accessToken(accessToken) + .accessTokenExpiresAt(accessTokenExpiresAt) + .refreshToken(refreshToken) + .build(); + + // Then + assertNotNull(tokenResponse); + assertEquals(accessToken, tokenResponse.getAccessToken()); + assertEquals(accessTokenExpiresAt, tokenResponse.getAccessTokenExpiresAt()); + assertEquals(refreshToken, tokenResponse.getRefreshToken()); + + } + + @Test + void testTokenResponseBuilder_DefaultValues() { + + // When + TokenResponse tokenResponse = TokenResponse.builder().build(); + + // Then + assertNotNull(tokenResponse); + assertNull(tokenResponse.getAccessToken()); + assertNull(tokenResponse.getAccessTokenExpiresAt()); + assertNull(tokenResponse.getRefreshToken()); + + } + + @Test + void testTokenResponseSettersAndGetters() { + + // Given + TokenResponse tokenResponse = new TokenResponse(); + + // When + tokenResponse.setAccessToken("newAccessToken"); + tokenResponse.setAccessTokenExpiresAt(System.currentTimeMillis() + 3600); + tokenResponse.setRefreshToken("newRefreshToken"); + + // Then + assertEquals("newAccessToken", tokenResponse.getAccessToken()); + assertNotNull(tokenResponse.getAccessTokenExpiresAt()); + assertEquals("newRefreshToken", tokenResponse.getRefreshToken()); + + } + + @Test + void testTokenResponseEquality() { + + // Given + TokenResponse tokenResponse1 = TokenResponse.builder() + .accessToken("token1") + .accessTokenExpiresAt(1234567890L) + .refreshToken("refreshToken1") + .build(); + + TokenResponse tokenResponse3 = TokenResponse.builder() + .accessToken("token2") + .accessTokenExpiresAt(9876543210L) + .refreshToken("refreshToken2") + .build(); + + // When & Then + + assertNotEquals(tokenResponse1, tokenResponse3); + + } + +} \ No newline at end of file diff --git a/authservice/src/test/java/com/springbootmicroservices/authservice/security/CustomAuthenticationEntryPointTest.java b/authservice/src/test/java/com/springbootmicroservices/authservice/security/CustomAuthenticationEntryPointTest.java new file mode 100644 index 0000000..b385c39 --- /dev/null +++ b/authservice/src/test/java/com/springbootmicroservices/authservice/security/CustomAuthenticationEntryPointTest.java @@ -0,0 +1,120 @@ +package com.springbootmicroservices.authservice.security; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.springbootmicroservices.authservice.exception.CustomError; +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.WriteListener; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.security.core.AuthenticationException; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +public class CustomAuthenticationEntryPointTest { + + private CustomAuthenticationEntryPoint customAuthenticationEntryPoint; + + @BeforeEach + public void setUp() { + customAuthenticationEntryPoint = new CustomAuthenticationEntryPoint(); + } + + @Test + public void testCommence() throws IOException { + // Mock objects + HttpServletRequest httpServletRequest = mock(HttpServletRequest.class); + HttpServletResponse httpServletResponse = mock(HttpServletResponse.class); + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + ServletOutputStream servletOutputStream = new ServletOutputStream() { + @Override + public void write(int b) throws IOException { + byteArrayOutputStream.write(b); + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setWriteListener(WriteListener writeListener) { + // No-op + } + }; + + // Set up the mocks + when(httpServletResponse.getOutputStream()).thenReturn(servletOutputStream); + + // Call the method to test + customAuthenticationEntryPoint.commence(httpServletRequest, httpServletResponse, new AuthenticationException("Test") {}); + + // Verify that the response status was set + verify(httpServletResponse).setStatus(HttpStatus.UNAUTHORIZED.value()); + verify(httpServletResponse).setContentType(MediaType.APPLICATION_JSON_VALUE); + + // Convert the response to a string and verify the content + String responseBody = byteArrayOutputStream.toString(); // Use ByteArrayOutputStream + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerModule(new JavaTimeModule()); + CustomError expectedCustomError = CustomError.builder() + .header(CustomError.Header.AUTH_ERROR.getName()) + .httpStatus(HttpStatus.UNAUTHORIZED) + .isSuccess(false) + .build(); + String expectedResponseBody = objectMapper.writeValueAsString(expectedCustomError); + + // Parse the JSON response and expected response + JsonNode responseNode = objectMapper.readTree(responseBody); + JsonNode expectedNode = objectMapper.readTree(expectedResponseBody); + + // Extract and format the 'time' fields + String responseTime = responseNode.get("time").asText(); + JsonNode expectedTimeNode = expectedNode.get("time"); + + // Define a DateTimeFormatter to compare up to minutes + DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME; + + // Parse the time strings into LocalDateTime objects + LocalDateTime responseDateTime = LocalDateTime.parse(responseTime, formatter); + LocalDateTime expectedDateTime = convertArrayToLocalDateTime(expectedTimeNode); + + // Truncate to minutes for comparison + responseDateTime = responseDateTime.truncatedTo(ChronoUnit.MINUTES); + expectedDateTime = expectedDateTime.truncatedTo(ChronoUnit.MINUTES); + + // Compare only the date and time up to minutes + assertEquals(expectedDateTime, responseDateTime); + } + + private LocalDateTime convertArrayToLocalDateTime(JsonNode timeNode) { + if (timeNode.isArray()) { + int year = timeNode.get(0).asInt(); + int month = timeNode.get(1).asInt(); + int day = timeNode.get(2).asInt(); + int hour = timeNode.get(3).asInt(); + int minute = timeNode.get(4).asInt(); + int second = timeNode.get(5).asInt(); + int nano = timeNode.get(6).asInt(); + return LocalDateTime.of(year, month, day, hour, minute, second, nano); + } + throw new IllegalArgumentException("Unexpected time format: " + timeNode); + } + +} diff --git a/authservice/src/test/java/com/springbootmicroservices/authservice/service/impl/LogoutServiceImplTest.java b/authservice/src/test/java/com/springbootmicroservices/authservice/service/impl/LogoutServiceImplTest.java new file mode 100644 index 0000000..01ce40a --- /dev/null +++ b/authservice/src/test/java/com/springbootmicroservices/authservice/service/impl/LogoutServiceImplTest.java @@ -0,0 +1,84 @@ +package com.springbootmicroservices.authservice.service.impl; + +import com.springbootmicroservices.authservice.base.AbstractBaseServiceTest; +import com.springbootmicroservices.authservice.client.UserServiceClient; +import com.springbootmicroservices.authservice.model.auth.dto.request.TokenInvalidateRequest; +import com.springbootmicroservices.authservice.model.common.dto.response.CustomResponse; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.http.HttpStatus; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +class LogoutServiceImplTest extends AbstractBaseServiceTest { + + @InjectMocks + private LogoutServiceImpl logoutService; + + @Mock + private UserServiceClient userServiceClient; + + @Test + void logout_ValidTokenInvalidateRequest_ReturnsCustomResponse() { + + // Given + TokenInvalidateRequest tokenInvalidateRequest = TokenInvalidateRequest.builder() + .accessToken("validAccessToken") + .refreshToken("validRefreshToken") + .build(); + + CustomResponse expectedResponse = CustomResponse.builder() + .httpStatus(HttpStatus.OK) + .isSuccess(true) + .build(); + + // When + when(userServiceClient.logout(any(TokenInvalidateRequest.class))) + .thenReturn(expectedResponse); + + CustomResponse result = logoutService.logout(tokenInvalidateRequest); + + // Then + assertNotNull(result); + assertTrue(result.getIsSuccess()); + assertEquals(HttpStatus.OK, result.getHttpStatus()); + + // Verify + verify(userServiceClient, times(1)).logout(any(TokenInvalidateRequest.class)); + + } + + @Test + public void logout_InvalidTokenInvalidateRequest_ReturnsErrorResponse() { + + // Given + TokenInvalidateRequest tokenInvalidateRequest = TokenInvalidateRequest.builder() + .accessToken("invalidAccessToken") + .refreshToken("invalidRefreshToken") + .build(); + + CustomResponse errorResponse = CustomResponse.builder() + .httpStatus(HttpStatus.UNAUTHORIZED) + .isSuccess(false) + .build(); + + // When + when(userServiceClient.logout(any(TokenInvalidateRequest.class))) + .thenReturn(errorResponse); + + // Then + CustomResponse result = logoutService.logout(tokenInvalidateRequest); + + assertNotNull(result); + assertFalse(result.getIsSuccess()); + assertEquals(HttpStatus.UNAUTHORIZED, result.getHttpStatus()); + + // Verify + verify(userServiceClient, times(1)).logout(any(TokenInvalidateRequest.class)); + + } + +} \ No newline at end of file diff --git a/authservice/src/test/java/com/springbootmicroservices/authservice/service/impl/RefreshTokenServiceImplTest.java b/authservice/src/test/java/com/springbootmicroservices/authservice/service/impl/RefreshTokenServiceImplTest.java new file mode 100644 index 0000000..5be88a6 --- /dev/null +++ b/authservice/src/test/java/com/springbootmicroservices/authservice/service/impl/RefreshTokenServiceImplTest.java @@ -0,0 +1,91 @@ +package com.springbootmicroservices.authservice.service.impl; + +import com.springbootmicroservices.authservice.base.AbstractBaseServiceTest; +import com.springbootmicroservices.authservice.client.UserServiceClient; +import com.springbootmicroservices.authservice.model.auth.dto.request.TokenRefreshRequest; +import com.springbootmicroservices.authservice.model.auth.dto.response.TokenResponse; +import com.springbootmicroservices.authservice.model.common.dto.response.CustomResponse; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +import org.mockito.Mockito; +import org.springframework.http.HttpStatus; + +class RefreshTokenServiceImplTest extends AbstractBaseServiceTest { + + @InjectMocks + private RefreshTokenServiceImpl refreshTokenService; + + @Mock + private UserServiceClient userServiceClient; + + + @Test + void refreshToken_ValidTokenRefreshRequest_ReturnsCustomResponse() { + + // Given + TokenRefreshRequest tokenRefreshRequest = TokenRefreshRequest.builder() + .refreshToken("validRefreshToken") + .build(); + + TokenResponse tokenResponse = TokenResponse.builder() + .accessToken("newAccessToken") + .accessTokenExpiresAt(System.currentTimeMillis() + 3600) + .refreshToken("newRefreshToken") + .build(); + + CustomResponse expectedResponse = CustomResponse.successOf(tokenResponse); + + // When + when(userServiceClient.refreshToken(any(TokenRefreshRequest.class))) + .thenReturn(expectedResponse); + + // Then + CustomResponse result = refreshTokenService.refreshToken(tokenRefreshRequest); + + assertNotNull(result); + assertTrue(result.getIsSuccess()); + assertEquals(HttpStatus.OK, result.getHttpStatus()); + assertEquals(tokenResponse, result.getResponse()); + + // Verify + verify(userServiceClient, times(1)).refreshToken(any(TokenRefreshRequest.class)); + + } + + @Test + void refreshToken_InvalidTokenRefreshRequest_ReturnsErrorResponse() { + + // Given + TokenRefreshRequest tokenRefreshRequest = TokenRefreshRequest.builder() + .refreshToken("invalidRefreshToken") + .build(); + + CustomResponse errorResponse = CustomResponse.builder() + .httpStatus(HttpStatus.UNAUTHORIZED) + .isSuccess(false) + .build(); + + // When + when(userServiceClient.refreshToken(any(TokenRefreshRequest.class))) + .thenReturn(errorResponse); + + // Then + CustomResponse result = refreshTokenService.refreshToken(tokenRefreshRequest); + + assertNotNull(result); + assertFalse(result.getIsSuccess()); + assertEquals(HttpStatus.UNAUTHORIZED, result.getHttpStatus()); + assertNull(result.getResponse()); + + // Verify + verify(userServiceClient, times(1)).refreshToken(any(TokenRefreshRequest.class)); + + } + +} \ No newline at end of file diff --git a/authservice/src/test/java/com/springbootmicroservices/authservice/service/impl/RegisterServiceImplTest.java b/authservice/src/test/java/com/springbootmicroservices/authservice/service/impl/RegisterServiceImplTest.java new file mode 100644 index 0000000..eb2693f --- /dev/null +++ b/authservice/src/test/java/com/springbootmicroservices/authservice/service/impl/RegisterServiceImplTest.java @@ -0,0 +1,95 @@ +package com.springbootmicroservices.authservice.service.impl; + +import com.springbootmicroservices.authservice.base.AbstractBaseServiceTest; +import com.springbootmicroservices.authservice.client.UserServiceClient; +import com.springbootmicroservices.authservice.model.auth.User; +import com.springbootmicroservices.authservice.model.auth.dto.request.RegisterRequest; +import com.springbootmicroservices.authservice.model.auth.enums.UserStatus; +import com.springbootmicroservices.authservice.model.auth.enums.UserType; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.Test; +import org.springframework.http.ResponseEntity; + +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +class RegisterServiceImplTest extends AbstractBaseServiceTest { + + @InjectMocks + private RegisterServiceImpl registerService; + + @Mock + private UserServiceClient userServiceClient; + + @Test + void registerUser_ValidRegisterRequest_ReturnsUser() { + + // Given + RegisterRequest registerRequest = RegisterRequest.builder() + .email("valid.email@example.com") + .password("validPassword123") + .firstName("John") + .lastName("Doe") + .phoneNumber("1234567890100") + .role("user") + .build(); + + User expectedUser = User.builder() + .id(UUID.randomUUID().toString()) + .email("valid.email@example.com") + .firstName("John") + .lastName("Doe") + .phoneNumber("1234567890100") + .userStatus(UserStatus.ACTIVE) + .userType(UserType.USER) + .build(); + + // When + when(userServiceClient.register(any(RegisterRequest.class))) + .thenReturn(ResponseEntity.ok(expectedUser)); + + // Then + User result = registerService.registerUser(registerRequest); + + assertNotNull(result); + assertEquals(expectedUser, result); + + // Verify + verify(userServiceClient, times(1)).register(any(RegisterRequest.class)); + + } + + @Test + void registerUser_InvalidRegisterRequest_ReturnsNull() { + + // Given + RegisterRequest registerRequest = RegisterRequest.builder() + .email("invalid.email") + .password("short") + .firstName("") + .lastName("") + .phoneNumber("123") + .role("") + .build(); + + // When + when(userServiceClient.register(any(RegisterRequest.class))) + .thenReturn(ResponseEntity.badRequest().build()); + + // Then + User result = registerService.registerUser(registerRequest); + + assertNull(result); + + // Verify + verify(userServiceClient, times(1)).register(any(RegisterRequest.class)); + + } + +} \ No newline at end of file diff --git a/authservice/src/test/java/com/springbootmicroservices/authservice/service/impl/UserLoginServiceImplTest.java b/authservice/src/test/java/com/springbootmicroservices/authservice/service/impl/UserLoginServiceImplTest.java new file mode 100644 index 0000000..3fa9fed --- /dev/null +++ b/authservice/src/test/java/com/springbootmicroservices/authservice/service/impl/UserLoginServiceImplTest.java @@ -0,0 +1,83 @@ +package com.springbootmicroservices.authservice.service.impl; + +import com.springbootmicroservices.authservice.base.AbstractBaseServiceTest; +import com.springbootmicroservices.authservice.client.UserServiceClient; +import com.springbootmicroservices.authservice.model.auth.dto.request.LoginRequest; +import com.springbootmicroservices.authservice.model.auth.dto.response.TokenResponse; +import com.springbootmicroservices.authservice.model.common.dto.response.CustomResponse; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.http.HttpStatus; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +class UserLoginServiceImplTest extends AbstractBaseServiceTest { + + @InjectMocks + private UserLoginServiceImpl userLoginService; + + @Mock + private UserServiceClient userServiceClient; + + @Test + void givenValidLoginRequest_whenLogin_ReturnsCustomResponse() { + + // Given + LoginRequest loginRequest = LoginRequest.builder() + .email("valid.email@example.com") + .password("validPassword123") + .build(); + TokenResponse tokenResponse = TokenResponse.builder() + .accessToken("access-token") + .accessTokenExpiresAt(System.currentTimeMillis() + 3600) + .refreshToken("refresh-token") + .build(); + CustomResponse customResponse = CustomResponse.successOf(tokenResponse); + + // When + when(userServiceClient.loginUser(any(LoginRequest.class))).thenReturn(customResponse); + + // Then + CustomResponse response = userLoginService.login(loginRequest); + + assertNotNull(response); + assertTrue(response.getIsSuccess()); + assertEquals(HttpStatus.OK, response.getHttpStatus()); + assertEquals(tokenResponse, response.getResponse()); + + // Verify + verify(userServiceClient, times(1)).loginUser(any(LoginRequest.class)); + + } + + @Test + void givenInvalidLoginRequest_whenLogin_ReturnsErrorResponse() { + + // Given + LoginRequest loginRequest = new LoginRequest(); + CustomResponse errorResponse = CustomResponse.builder() + .httpStatus(HttpStatus.UNAUTHORIZED) + .isSuccess(false) + .build(); + + // When + when(userServiceClient.loginUser(any(LoginRequest.class))).thenReturn(errorResponse); + + + // Then + CustomResponse response = userLoginService.login(loginRequest); + + assertNotNull(response); + assertFalse(response.getIsSuccess()); + assertEquals(HttpStatus.UNAUTHORIZED, response.getHttpStatus()); + assertNull(response.getResponse()); + + // Verify + verify(userServiceClient, times(1)).loginUser(any(LoginRequest.class)); + + } + +} \ No newline at end of file diff --git a/userservice/src/main/java/com/springbootmicroservices/userservice/exception/handler/GlobalExceptionHandler.java b/userservice/src/main/java/com/springbootmicroservices/userservice/exception/handler/GlobalExceptionHandler.java index e296af1..edb3b7e 100644 --- a/userservice/src/main/java/com/springbootmicroservices/userservice/exception/handler/GlobalExceptionHandler.java +++ b/userservice/src/main/java/com/springbootmicroservices/userservice/exception/handler/GlobalExceptionHandler.java @@ -112,10 +112,10 @@ protected ResponseEntity handleRuntimeException(final RuntimeException runtim * @return ResponseEntity with CustomError containing details of the exception. */ @ExceptionHandler(PasswordNotValidException.class) - protected ResponseEntity handlePasswordNotValidException(final PasswordNotValidException ex) { + protected ResponseEntity handlePasswordNotValidException(final PasswordNotValidException ex) { CustomError customError = CustomError.builder() .httpStatus(HttpStatus.BAD_REQUEST) - .header(CustomError.Header.API_ERROR.getName()) + .header(CustomError.Header.VALIDATION_ERROR.getName()) .message(ex.getMessage()) .build(); diff --git a/userservice/src/main/java/com/springbootmicroservices/userservice/service/impl/TokenServiceImpl.java b/userservice/src/main/java/com/springbootmicroservices/userservice/service/impl/TokenServiceImpl.java index 60caf2d..df1d032 100644 --- a/userservice/src/main/java/com/springbootmicroservices/userservice/service/impl/TokenServiceImpl.java +++ b/userservice/src/main/java/com/springbootmicroservices/userservice/service/impl/TokenServiceImpl.java @@ -156,6 +156,9 @@ public void verifyAndValidate(final String jwt) { log.info("Token is valid"); + } catch (ExpiredJwtException e) { + log.error("Token has expired", e); + throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Token has expired", e); } catch (JwtException e) { log.error("Invalid JWT token", e); throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid JWT token", e); diff --git a/userservice/src/test/java/com/springbootmicroservices/userservice/controller/UserControllerTest.java b/userservice/src/test/java/com/springbootmicroservices/userservice/controller/UserControllerTest.java index 5f6f879..961e3b9 100644 --- a/userservice/src/test/java/com/springbootmicroservices/userservice/controller/UserControllerTest.java +++ b/userservice/src/test/java/com/springbootmicroservices/userservice/controller/UserControllerTest.java @@ -10,14 +10,12 @@ import com.springbootmicroservices.userservice.model.user.dto.request.TokenRefreshRequest; import com.springbootmicroservices.userservice.model.user.dto.response.TokenResponse; import com.springbootmicroservices.userservice.model.user.mapper.TokenToTokenResponseMapper; -import com.springbootmicroservices.userservice.service.LogoutService; -import com.springbootmicroservices.userservice.service.RefreshTokenService; -import com.springbootmicroservices.userservice.service.RegisterService; -import com.springbootmicroservices.userservice.service.UserLoginService; +import com.springbootmicroservices.userservice.service.*; import org.junit.jupiter.api.Test; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultHandlers; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; @@ -42,6 +40,9 @@ class UserControllerTest extends AbstractRestControllerTest { @MockBean private LogoutService userLogoutService; + @MockBean + private TokenService tokenService; + private final TokenToTokenResponseMapper tokenToTokenResponseMapper = TokenToTokenResponseMapper.initialize(); @Test @@ -181,4 +182,47 @@ void givenTokenInvalidateRequest_WhenLogoutForUser_ThenReturnInvalidateToken() t } + @Test + void givenValidToken_whenValidateToken_thenReturnOk() throws Exception { + + // Given + String validToken = "validToken"; + + // When + doNothing().when(tokenService).verifyAndValidate(validToken); + + // Then + mockMvc.perform(MockMvcRequestBuilders.post("/api/v1/users/validate-token") + .param("token", validToken)) + .andDo(MockMvcResultHandlers.print()) + .andExpect(MockMvcResultMatchers.status().isOk()); + + // Verify + verify(tokenService, times(1)).verifyAndValidate(validToken); + + } + + @Test + void givenValidToken_whenGetAuthentication_thenReturnAuthentication() throws Exception { + + // Given + String validToken = "validToken"; + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken("user", "password"); + + // When + when(tokenService.getAuthentication(validToken)).thenReturn(authenticationToken); + + // Then + mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/users/authenticate") + .param("token", validToken)) + .andDo(MockMvcResultHandlers.print()) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.jsonPath("$.principal").value("user")) + .andExpect(MockMvcResultMatchers.jsonPath("$.credentials").value("password")); + + // Verify + verify(tokenService, times(1)).getAuthentication(validToken); + + } + } \ No newline at end of file diff --git a/userservice/src/test/java/com/springbootmicroservices/userservice/exception/PasswordNotValidExceptionTest.java b/userservice/src/test/java/com/springbootmicroservices/userservice/exception/PasswordNotValidExceptionTest.java new file mode 100644 index 0000000..746fcef --- /dev/null +++ b/userservice/src/test/java/com/springbootmicroservices/userservice/exception/PasswordNotValidExceptionTest.java @@ -0,0 +1,39 @@ +package com.springbootmicroservices.userservice.exception; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class PasswordNotValidExceptionTest { + + @Test + void testDefaultConstructor() { + + // Arrange + String errorMessage = """ + Password is not valid! + """; + + // Act + PasswordNotValidException exception = new PasswordNotValidException(); + + // Assert + assertEquals(errorMessage, exception.getMessage()); + + } + + @Test + void testParameterizedConstructor() { + + // Arrange + String customMessage = "Custom message"; + + // Act + PasswordNotValidException exception = new PasswordNotValidException(customMessage); + + // Assert + assertEquals("Password is not valid!\n Custom message", exception.getMessage()); + + } + +} \ No newline at end of file diff --git a/userservice/src/test/java/com/springbootmicroservices/userservice/exception/TokenAlreadyInvalidatedExceptionTest.java b/userservice/src/test/java/com/springbootmicroservices/userservice/exception/TokenAlreadyInvalidatedExceptionTest.java new file mode 100644 index 0000000..b2a1ca7 --- /dev/null +++ b/userservice/src/test/java/com/springbootmicroservices/userservice/exception/TokenAlreadyInvalidatedExceptionTest.java @@ -0,0 +1,42 @@ +package com.springbootmicroservices.userservice.exception; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class TokenAlreadyInvalidatedExceptionTest { + + @Test + void testDefaultConstructor() { + + // Arrange + String errorMessage = """ + Token is already invalidated! + """; + + // Act + TokenAlreadyInvalidatedException exception = new TokenAlreadyInvalidatedException(); + + // Assert + assertEquals(errorMessage, exception.getMessage()); + + } + + @Test + void testParameterizedConstructor() { + + // Arrange + String tokenId = "12345"; + String expectedMessage = """ + Token is already invalidated! + TokenID = 12345"""; + + // Act + TokenAlreadyInvalidatedException exception = new TokenAlreadyInvalidatedException(tokenId); + + // Assert + assertEquals(expectedMessage, exception.getMessage()); + + } + +} \ No newline at end of file diff --git a/userservice/src/test/java/com/springbootmicroservices/userservice/exception/UserAlreadyExistExceptionTest.java b/userservice/src/test/java/com/springbootmicroservices/userservice/exception/UserAlreadyExistExceptionTest.java new file mode 100644 index 0000000..61594a6 --- /dev/null +++ b/userservice/src/test/java/com/springbootmicroservices/userservice/exception/UserAlreadyExistExceptionTest.java @@ -0,0 +1,42 @@ +package com.springbootmicroservices.userservice.exception; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class UserAlreadyExistExceptionTest { + + @Test + void testDefaultConstructor() { + + // Arrange + String errorMessage = """ + User already exist! + """; + + // Act + UserAlreadyExistException exception = new UserAlreadyExistException(); + + // Assert + assertEquals(errorMessage, exception.getMessage()); + + } + + @Test + void testParameterizedConstructor() { + + // Arrange + String customMessage = "Custom message"; + String expectedMessage = """ + User already exist! + Custom message"""; + + // Act + UserAlreadyExistException exception = new UserAlreadyExistException(customMessage); + + // Assert + assertEquals(expectedMessage, exception.getMessage()); + + } + +} \ No newline at end of file diff --git a/userservice/src/test/java/com/springbootmicroservices/userservice/exception/UserNotFoundExceptionTest.java b/userservice/src/test/java/com/springbootmicroservices/userservice/exception/UserNotFoundExceptionTest.java new file mode 100644 index 0000000..0c17380 --- /dev/null +++ b/userservice/src/test/java/com/springbootmicroservices/userservice/exception/UserNotFoundExceptionTest.java @@ -0,0 +1,42 @@ +package com.springbootmicroservices.userservice.exception; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class UserNotFoundExceptionTest { + + @Test + void testDefaultConstructor() { + + // Arrange + String errorMessage = """ + User not found! + """; + + // Act + UserNotFoundException exception = new UserNotFoundException(); + + // Assert + assertEquals(errorMessage, exception.getMessage()); + + } + + @Test + void testParameterizedConstructor() { + + // Arrange + String customMessage = "Custom message"; + String expectedMessage = """ + User not found! + Custom message"""; + + // Act + UserNotFoundException exception = new UserNotFoundException(customMessage); + + // Assert + assertEquals(expectedMessage, exception.getMessage()); + + } + +} \ No newline at end of file diff --git a/userservice/src/test/java/com/springbootmicroservices/userservice/exception/UserStatusNotValidExceptionTest.java b/userservice/src/test/java/com/springbootmicroservices/userservice/exception/UserStatusNotValidExceptionTest.java new file mode 100644 index 0000000..1323dd4 --- /dev/null +++ b/userservice/src/test/java/com/springbootmicroservices/userservice/exception/UserStatusNotValidExceptionTest.java @@ -0,0 +1,42 @@ +package com.springbootmicroservices.userservice.exception; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class UserStatusNotValidExceptionTest { + + @Test + void testDefaultConstructor() { + + // Arrange + String errorMessage = """ + User status is not valid! + """; + + // Act + UserStatusNotValidException exception = new UserStatusNotValidException(); + + // Assert + assertEquals(errorMessage, exception.getMessage()); + + } + + @Test + void testParameterizedConstructor() { + + // Arrange + String customMessage = "Custom message"; + String expectedMessage = """ + User status is not valid! + Custom message"""; + + // Act + UserStatusNotValidException exception = new UserStatusNotValidException(customMessage); + + // Assert + assertEquals(expectedMessage, exception.getMessage()); + + } + +} \ No newline at end of file diff --git a/userservice/src/test/java/com/springbootmicroservices/userservice/exception/handler/GlobalExceptionHandlerTest.java b/userservice/src/test/java/com/springbootmicroservices/userservice/exception/handler/GlobalExceptionHandlerTest.java new file mode 100644 index 0000000..ede1f6c --- /dev/null +++ b/userservice/src/test/java/com/springbootmicroservices/userservice/exception/handler/GlobalExceptionHandlerTest.java @@ -0,0 +1,265 @@ +package com.springbootmicroservices.userservice.exception.handler; + +import static org.junit.jupiter.api.Assertions.*; + +import com.springbootmicroservices.userservice.base.AbstractRestControllerTest; +import com.springbootmicroservices.userservice.exception.*; +import com.springbootmicroservices.userservice.model.common.CustomError; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import jakarta.validation.Path; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.validation.BindingResult; +import org.springframework.validation.FieldError; +import org.springframework.validation.ObjectError; +import org.springframework.web.bind.MethodArgumentNotValidException; + +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class GlobalExceptionHandlerTest extends AbstractRestControllerTest { + + @InjectMocks + private GlobalExceptionHandler globalExceptionHandler; + + @Test + void givenMethodArgumentNotValidException_whenHandleMethodArgumentNotValid_thenRespondWithBadRequest() { + + // Given + BindingResult bindingResult = mock(BindingResult.class); + MethodArgumentNotValidException ex = new MethodArgumentNotValidException(null, bindingResult); + FieldError fieldError = new FieldError("objectName", "fieldName", "error message"); + List objectErrors = Collections.singletonList(fieldError); + + when(bindingResult.getAllErrors()).thenReturn(objectErrors); + + CustomError expectedError = CustomError.builder() + .httpStatus(HttpStatus.BAD_REQUEST) + .header(CustomError.Header.VALIDATION_ERROR.getName()) + .message("Validation failed") + .subErrors(Collections.singletonList( + CustomError.CustomSubError.builder() + .field("fieldName") + .message("error message") + .build())) + .build(); + + // When + ResponseEntity responseEntity = globalExceptionHandler.handleMethodArgumentNotValid(ex); + + // Then + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); + CustomError actualError = (CustomError) responseEntity.getBody(); + checkCustomError(expectedError, actualError); + + } + + @Test + void givenConstraintViolationException_whenHandlePathVariableErrors_thenRespondWithBadRequest() { + + // Given + ConstraintViolation mockViolation = mock(ConstraintViolation.class); + Path mockPath = mock(Path.class); + Set> violations = Set.of(mockViolation); + ConstraintViolationException mockException = new ConstraintViolationException(violations); + + CustomError.CustomSubError subError = CustomError.CustomSubError.builder() + .message("must not be null") + .field("") + .value("invalid value") + .type("String") // Default to String if getRootBeanClass() is null + .build(); + + CustomError expectedError = CustomError.builder() + .time(LocalDateTime.now()) + .httpStatus(HttpStatus.BAD_REQUEST) + .header(CustomError.Header.VALIDATION_ERROR.getName()) + .message("Constraint violation") + .subErrors(Collections.singletonList(subError)) + .build(); + + // When + when(mockViolation.getMessage()).thenReturn("must not be null"); + when(mockViolation.getPropertyPath()).thenReturn(mockPath); + when(mockPath.toString()).thenReturn("field"); + when(mockViolation.getInvalidValue()).thenReturn("invalid value"); + when(mockViolation.getRootBeanClass()).thenReturn(String.class); // Ensure this does not return null + + // Then + ResponseEntity responseEntity = globalExceptionHandler.handlePathVariableErrors(mockException); + + CustomError actualError = (CustomError) responseEntity.getBody(); + + // Verify + checkCustomError(expectedError, actualError); + + } + + @Test + void givenRuntimeException_whenHandleRuntimeException_thenRespondWithNotFound() { + + // Given + RuntimeException ex = new RuntimeException("Runtime exception message"); + + CustomError expectedError = CustomError.builder() + .httpStatus(HttpStatus.NOT_FOUND) + .header(CustomError.Header.API_ERROR.getName()) + .message("Runtime exception message") + .build(); + + // When + ResponseEntity responseEntity = globalExceptionHandler.handleRuntimeException(ex); + + // Then + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); + + CustomError actualError = (CustomError) responseEntity.getBody(); + checkCustomError(expectedError, actualError); + + } + + @Test + void givenPasswordNotValidException_whenHandlePasswordNotValidException_thenRespondWithBadRequest() { + + // Given + PasswordNotValidException ex = new PasswordNotValidException("Password not valid message"); + + CustomError expectedError = CustomError.builder() + .httpStatus(HttpStatus.BAD_REQUEST) + .header(CustomError.Header.VALIDATION_ERROR.getName()) + .message("Password is not valid!\n Password not valid message") + .isSuccess(false) + .build(); + + // When + ResponseEntity responseEntity = globalExceptionHandler.handlePasswordNotValidException(ex); + + // Then + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); + CustomError actualError = responseEntity.getBody(); + checkCustomError(expectedError, actualError); + + } + + @Test + void givenTokenAlreadyInvalidatedException_whenHandleTokenAlreadyInvalidatedException_thenRespondWithBadRequest() { + + // Given + TokenAlreadyInvalidatedException ex = new TokenAlreadyInvalidatedException(); + + CustomError expectedError = CustomError.builder() + .httpStatus(HttpStatus.BAD_REQUEST) + .header(CustomError.Header.API_ERROR.getName()) + .message("Token is already invalidated!\n") + .build(); + + // When + ResponseEntity responseEntity = globalExceptionHandler.handleTokenAlreadyInvalidatedException(ex); + + // Then + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); + CustomError actualError = (CustomError) responseEntity.getBody(); + checkCustomError(expectedError, actualError); + + } + + @Test + void givenUserAlreadyExistException_whenHandleUserAlreadyExistException_thenRespondWithConflict() { + + // Given + UserAlreadyExistException ex = new UserAlreadyExistException(); + + CustomError expectedError = CustomError.builder() + .httpStatus(HttpStatus.CONFLICT) + .header(CustomError.Header.API_ERROR.getName()) + .message("User already exist!\n") + .build(); + + // When + ResponseEntity responseEntity = globalExceptionHandler.handleUserAlreadyExistException(ex); + + // Then + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.CONFLICT); + CustomError actualError = (CustomError) responseEntity.getBody(); + checkCustomError(expectedError, actualError); + + } + + @Test + void givenUserNotFoundException_whenHandleUserNotFoundException_thenRespondWithNotFound() { + + // Given + UserNotFoundException ex = new UserNotFoundException(); + + CustomError expectedError = CustomError.builder() + .httpStatus(HttpStatus.NOT_FOUND) + .header(CustomError.Header.API_ERROR.getName()) + .message("User not found!\n") + .build(); + + // When + ResponseEntity responseEntity = globalExceptionHandler.handleUserNotFoundException(ex); + + // Then + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); + CustomError actualError = (CustomError) responseEntity.getBody(); + checkCustomError(expectedError, actualError); + + } + + @Test + void givenUserStatusNotValidException_whenHandleUserStatusNotValidException_thenRespondWithBadRequest() { + + // Given + UserStatusNotValidException ex = new UserStatusNotValidException(); + + CustomError expectedError = CustomError.builder() + .httpStatus(HttpStatus.BAD_REQUEST) + .header(CustomError.Header.API_ERROR.getName()) + .message("User status is not valid!\n") + .build(); + + // When + ResponseEntity responseEntity = globalExceptionHandler.handleUserStatusNotValidException(ex); + + // Then + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); + CustomError actualError = (CustomError) responseEntity.getBody(); + checkCustomError(expectedError, actualError); + + } + + + private void checkCustomError(CustomError expectedError, CustomError actualError) { + + assertThat(actualError).isNotNull(); + assertThat(actualError.getTime()).isNotNull(); + assertThat(actualError.getHeader()).isEqualTo(expectedError.getHeader()); + assertThat(actualError.getIsSuccess()).isEqualTo(expectedError.getIsSuccess()); + + if (expectedError.getMessage() != null) { + assertThat(actualError.getMessage()).isEqualTo(expectedError.getMessage()); + } + + if (expectedError.getSubErrors() != null) { + assertThat(actualError.getSubErrors().size()).isEqualTo(expectedError.getSubErrors().size()); + if (!expectedError.getSubErrors().isEmpty()) { + assertThat(actualError.getSubErrors().get(0).getMessage()).isEqualTo(expectedError.getSubErrors().get(0).getMessage()); + assertThat(actualError.getSubErrors().get(0).getField()).isEqualTo(expectedError.getSubErrors().get(0).getField()); + assertThat(actualError.getSubErrors().get(0).getValue()).isEqualTo(expectedError.getSubErrors().get(0).getValue()); + assertThat(actualError.getSubErrors().get(0).getType()).isEqualTo(expectedError.getSubErrors().get(0).getType()); + } + } + } + +} \ No newline at end of file diff --git a/userservice/src/test/java/com/springbootmicroservices/userservice/security/CustomAuthenticationEntryPointTest.java b/userservice/src/test/java/com/springbootmicroservices/userservice/security/CustomAuthenticationEntryPointTest.java new file mode 100644 index 0000000..4967431 --- /dev/null +++ b/userservice/src/test/java/com/springbootmicroservices/userservice/security/CustomAuthenticationEntryPointTest.java @@ -0,0 +1,122 @@ +package com.springbootmicroservices.userservice.security; + +import static org.junit.jupiter.api.Assertions.*; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.springbootmicroservices.userservice.model.common.CustomError; +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.WriteListener; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.security.core.AuthenticationException; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +public class CustomAuthenticationEntryPointTest { + + private CustomAuthenticationEntryPoint customAuthenticationEntryPoint; + + @BeforeEach + public void setUp() { + customAuthenticationEntryPoint = new CustomAuthenticationEntryPoint(); + } + + @Test + public void testCommence() throws IOException { + // Mock objects + HttpServletRequest httpServletRequest = mock(HttpServletRequest.class); + HttpServletResponse httpServletResponse = mock(HttpServletResponse.class); + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + ServletOutputStream servletOutputStream = new ServletOutputStream() { + @Override + public void write(int b) throws IOException { + byteArrayOutputStream.write(b); + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setWriteListener(WriteListener writeListener) { + // No-op + } + }; + + // Set up the mocks + when(httpServletResponse.getOutputStream()).thenReturn(servletOutputStream); + + // Call the method to test + customAuthenticationEntryPoint.commence(httpServletRequest, httpServletResponse, new AuthenticationException("Test") {}); + + // Verify that the response status was set + verify(httpServletResponse).setStatus(HttpStatus.UNAUTHORIZED.value()); + verify(httpServletResponse).setContentType(MediaType.APPLICATION_JSON_VALUE); + + // Convert the response to a string and verify the content + String responseBody = byteArrayOutputStream.toString(); // Use ByteArrayOutputStream + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerModule(new JavaTimeModule()); + CustomError expectedCustomError = CustomError.builder() + .header(CustomError.Header.AUTH_ERROR.getName()) + .httpStatus(HttpStatus.UNAUTHORIZED) + .isSuccess(false) + .build(); + String expectedResponseBody = objectMapper.writeValueAsString(expectedCustomError); + + // Parse the JSON response and expected response + JsonNode responseNode = objectMapper.readTree(responseBody); + JsonNode expectedNode = objectMapper.readTree(expectedResponseBody); + + // Extract and format the 'time' fields + String responseTime = responseNode.get("time").asText(); + JsonNode expectedTimeNode = expectedNode.get("time"); + + // Define a DateTimeFormatter to compare up to minutes + DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME; + + // Parse the time strings into LocalDateTime objects + LocalDateTime responseDateTime = LocalDateTime.parse(responseTime, formatter); + LocalDateTime expectedDateTime = convertArrayToLocalDateTime(expectedTimeNode); + + // Truncate to minutes for comparison + responseDateTime = responseDateTime.truncatedTo(ChronoUnit.MINUTES); + expectedDateTime = expectedDateTime.truncatedTo(ChronoUnit.MINUTES); + + // Compare only the date and time up to minutes + assertEquals(expectedDateTime, responseDateTime); + } + + private LocalDateTime convertArrayToLocalDateTime(JsonNode timeNode) { + if (timeNode.isArray()) { + int year = timeNode.get(0).asInt(); + int month = timeNode.get(1).asInt(); + int day = timeNode.get(2).asInt(); + int hour = timeNode.get(3).asInt(); + int minute = timeNode.get(4).asInt(); + int second = timeNode.get(5).asInt(); + int nano = timeNode.get(6).asInt(); + return LocalDateTime.of(year, month, day, hour, minute, second, nano); + } + throw new IllegalArgumentException("Unexpected time format: " + timeNode); + } + +} diff --git a/userservice/src/test/java/com/springbootmicroservices/userservice/service/impl/InvalidTokenServiceImplTest.java b/userservice/src/test/java/com/springbootmicroservices/userservice/service/impl/InvalidTokenServiceImplTest.java new file mode 100644 index 0000000..98e0763 --- /dev/null +++ b/userservice/src/test/java/com/springbootmicroservices/userservice/service/impl/InvalidTokenServiceImplTest.java @@ -0,0 +1,87 @@ +package com.springbootmicroservices.userservice.service.impl; + +import com.springbootmicroservices.userservice.base.AbstractBaseServiceTest; +import com.springbootmicroservices.userservice.exception.TokenAlreadyInvalidatedException; +import com.springbootmicroservices.userservice.model.user.entity.InvalidTokenEntity; +import com.springbootmicroservices.userservice.repository.InvalidTokenRepository; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import java.util.Optional; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class InvalidTokenServiceImplTest extends AbstractBaseServiceTest { + + @InjectMocks + private InvalidTokenServiceImpl invalidTokenService; + + @Mock + private InvalidTokenRepository invalidTokenRepository; + + @Test + void givenTokenIds_whenInvalidateTokens_thenSaveAllTokens() { + + // Given + Set tokenIds = Set.of("token1", "token2"); + + // When + invalidTokenService.invalidateTokens(tokenIds); + + // Then + ArgumentCaptor> captor = ArgumentCaptor.forClass(Set.class); + verify(invalidTokenRepository).saveAll(captor.capture()); + Set capturedTokens = captor.getValue(); + + assertThat(capturedTokens) + .hasSize(2) + .extracting("tokenId") + .containsExactlyInAnyOrder("token1", "token2"); + + } + + @Test + void givenInvalidToken_whenCheckForInvalidityOfToken_thenThrowTokenAlreadyInvalidatedException() { + + // Given + String tokenId = "invalidToken"; + InvalidTokenEntity invalidTokenEntity = InvalidTokenEntity.builder().tokenId(tokenId).build(); + + // When + when(invalidTokenRepository.findByTokenId(tokenId)).thenReturn(Optional.of(invalidTokenEntity)); + + // Then + assertThatThrownBy(() -> invalidTokenService.checkForInvalidityOfToken(tokenId)) + .isInstanceOf(TokenAlreadyInvalidatedException.class) + .hasMessageContaining(tokenId); + + // Verify + verify(invalidTokenRepository).findByTokenId(tokenId); + + } + + @Test + void givenValidToken_whenCheckForInvalidityOfToken_thenDoNotThrowException() { + + // Given + String tokenId = "validToken"; + + // When + when(invalidTokenRepository.findByTokenId(tokenId)).thenReturn(Optional.empty()); + + // Then + invalidTokenService.checkForInvalidityOfToken(tokenId); + + // Verify + verify(invalidTokenRepository).findByTokenId(tokenId); + + } + +} \ No newline at end of file diff --git a/userservice/src/test/java/com/springbootmicroservices/userservice/service/impl/TokenServiceImplTest.java b/userservice/src/test/java/com/springbootmicroservices/userservice/service/impl/TokenServiceImplTest.java new file mode 100644 index 0000000..9d5de03 --- /dev/null +++ b/userservice/src/test/java/com/springbootmicroservices/userservice/service/impl/TokenServiceImplTest.java @@ -0,0 +1,305 @@ +package com.springbootmicroservices.userservice.service.impl; + +import com.springbootmicroservices.userservice.base.AbstractBaseServiceTest; +import com.springbootmicroservices.userservice.config.TokenConfigurationParameter; +import com.springbootmicroservices.userservice.model.user.Token; +import com.springbootmicroservices.userservice.model.user.enums.TokenClaims; +import com.springbootmicroservices.userservice.model.user.enums.UserType; +import com.springbootmicroservices.userservice.service.InvalidTokenService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.*; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.web.server.ResponseStatusException; + +import io.jsonwebtoken.*; +import io.jsonwebtoken.security.Keys; + +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.*; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.*; +class TokenServiceImplTest extends AbstractBaseServiceTest { + + @InjectMocks + private TokenServiceImpl tokenService; + + @Mock + private InvalidTokenService invalidTokenService; + + @Mock + private TokenConfigurationParameter tokenConfigurationParameter; + + private KeyPair keyPair; + + @BeforeEach + void setUp() { + keyPair = Keys.keyPairFor(SignatureAlgorithm.RS256); + when(tokenConfigurationParameter.getPrivateKey()).thenReturn(keyPair.getPrivate()); + when(tokenConfigurationParameter.getPublicKey()).thenReturn(keyPair.getPublic()); + } + + @Test + void givenClaims_whenGenerateToken_thenReturnValidToken() { + + // Given + Map claims = Map.of("user_id", "12345"); + when(tokenConfigurationParameter.getAccessTokenExpireMinute()).thenReturn(15); + when(tokenConfigurationParameter.getRefreshTokenExpireDay()).thenReturn(30); + + // When + Token token = tokenService.generateToken(claims); + + // Then + assertThat(token).isNotNull(); + assertThat(token.getAccessToken()).isNotEmpty(); + assertThat(token.getRefreshToken()).isNotEmpty(); + + } + + @Test + void givenClaimsAndRefreshToken_whenGenerateToken_thenReturnValidToken() { + + // Given + Map claims = Map.of("user_id", "12345"); + String refreshToken = Jwts.builder() + .id(UUID.randomUUID().toString()) + .issuedAt(new Date()) + .expiration(new Date(System.currentTimeMillis() + 86400000L)) // 1 day expiration + .signWith(keyPair.getPrivate()) + .compact(); + + when(tokenConfigurationParameter.getAccessTokenExpireMinute()).thenReturn(15); + + doNothing().when(invalidTokenService).checkForInvalidityOfToken(anyString()); + + // When + Token token = tokenService.generateToken(claims, refreshToken); + + // Then + assertThat(token).isNotNull(); + assertThat(token.getAccessToken()).isNotEmpty(); + assertThat(token.getRefreshToken()).isEqualTo(refreshToken); + + } + + @Test + void givenToken_whenGetAuthentication_thenReturnAuthentication() { + + // Given + String token = Jwts.builder() + .claim(TokenClaims.USER_ID.getValue(), "12345") + .claim(TokenClaims.USER_TYPE.getValue(), "ADMIN") + .issuedAt(new Date()) + .expiration(new Date(System.currentTimeMillis() + 86400000L)) // 1 day expiration + .setHeaderParam(Header.TYPE, "JWT") // Add type information + .signWith(keyPair.getPrivate()) + .compact(); + + final Jws claimsJws = Jwts.parser() + .verifyWith(keyPair.getPublic()) + .build() + .parseSignedClaims(token); + + final JwsHeader jwsHeader = claimsJws.getHeader(); + final Claims payload = claimsJws.getBody(); + + // Handle potential null values for jwsHeader + String tokenType = jwsHeader.getType() != null ? jwsHeader.getType() : ""; + String algorithm = jwsHeader.getAlgorithm() != null ? jwsHeader.getAlgorithm() : ""; + + // Verify the created Jwt object + final org.springframework.security.oauth2.jwt.Jwt jwt = new org.springframework.security.oauth2.jwt.Jwt( + token, + payload.getIssuedAt().toInstant(), + payload.getExpiration().toInstant(), + Map.of( + TokenClaims.TYP.getValue(), tokenType, + TokenClaims.ALGORITHM.getValue(), algorithm + ), + payload + ); + + final UserType userType = UserType.valueOf(payload.get(TokenClaims.USER_TYPE.getValue()).toString()); + + final List authorities = new ArrayList<>(); + authorities.add(new SimpleGrantedAuthority(userType.name())); + + // When + UsernamePasswordAuthenticationToken authentication = tokenService.getAuthentication(token); + + // Then + assertThat(authentication).isNotNull(); + assertThat(authentication.getAuthorities()).containsExactly(new SimpleGrantedAuthority("ADMIN")); + assertThat(authentication.getPrincipal()).isEqualTo(jwt); + + } + + @Test + void givenValidToken_whenVerifyAndValidate_thenLogTokenIsValid() { + // Given + String token = Jwts.builder() + .claim("user_id", "12345") + .issuedAt(new Date()) + .expiration(new Date(System.currentTimeMillis() + 86400000L)) // 1 day expiration + .signWith(keyPair.getPrivate()) + .compact(); + + // When & Then + tokenService.verifyAndValidate(token); + + } + + @Test + void givenTokens_whenVerifyAndValidate_thenValidateEachToken() { + // Given + Set tokens = Set.of( + Jwts.builder().claim("user_id", "12345").issuedAt(new Date()).expiration(new Date(System.currentTimeMillis() + 86400000L)).signWith(keyPair.getPrivate()).compact(), + Jwts.builder().claim("user_id", "67890").issuedAt(new Date()).expiration(new Date(System.currentTimeMillis() + 86400000L)).signWith(keyPair.getPrivate()).compact() + ); + + // When + tokenService.verifyAndValidate(tokens); + + } + + @Test + void givenJwt_whenGetClaims_thenReturnClaims() { + // Given + String token = Jwts.builder() + .claim("user_id", "12345") + .issuedAt(new Date()) + .expiration(new Date(System.currentTimeMillis() + 86400000L)) // 1 day expiration + .signWith(keyPair.getPrivate()) + .compact(); + + // When + Jws claims = tokenService.getClaims(token); + + // Then + assertThat(claims.getBody().get("user_id")).isEqualTo("12345"); + } + + @Test + void givenJwt_whenGetPayload_thenReturnPayload() { + // Given + String token = Jwts.builder() + .claim("user_id", "12345") + .issuedAt(new Date()) + .expiration(new Date(System.currentTimeMillis() + 86400000L)) // 1 day expiration + .signWith(keyPair.getPrivate()) + .compact(); + + // When + Claims payload = tokenService.getPayload(token); + + // Then + assertThat(payload.get("user_id")).isEqualTo("12345"); + + } + + @Test + void givenJwt_whenGetId_thenReturnId() { + + // Given + String tokenId = UUID.randomUUID().toString(); + String token = Jwts.builder() + .id(tokenId) + .issuedAt(new Date()) + .expiration(new Date(System.currentTimeMillis() + 86400000L)) // 1 day expiration + .signWith(keyPair.getPrivate()) + .compact(); + + // When + String id = tokenService.getId(token); + + // Then + assertThat(id).isEqualTo(tokenId); + + } + + @Test + void givenMalformedToken_whenVerifyAndValidate_thenThrowJwtException() { + + // Given + String malformedToken = "malformed.token.string"; + + // When & Then + assertThatThrownBy(() -> tokenService.verifyAndValidate(malformedToken)) + .isInstanceOf(ResponseStatusException.class) + .hasMessageContaining("Invalid JWT token") + .hasCauseInstanceOf(JwtException.class); + + } + + @Test + void givenTokenWithInvalidSignature_whenVerifyAndValidate_thenThrowJwtException() { + // Given + String validToken = Jwts.builder() + .claim("user_id", "12345") + .issuedAt(new Date()) + .expiration(new Date(System.currentTimeMillis() + 86400000L)) // 1 day expiration + .signWith(keyPair.getPrivate()) // Sign with valid private key + .compact(); + + // Tamper with the token by modifying the signature + String tamperedToken = validToken + "tampered"; // Simple tampering for illustration + + // When & Then + assertThatThrownBy(() -> tokenService.verifyAndValidate(tamperedToken)) + .isInstanceOf(ResponseStatusException.class) + .hasMessageContaining("Invalid JWT token") + .hasCauseInstanceOf(JwtException.class); + } + + @Test + void givenToken_whenVerifyAndValidate_thenThrowResponseStatusExceptionOnUnexpectedError() { + // Given + String token = Jwts.builder() + .claim("user_id", "12345") + .issuedAt(new Date()) + .expiration(new Date(System.currentTimeMillis() + 86400000L)) // 1 day expiration + .signWith(keyPair.getPrivate()) + .compact(); + + // Mock the parser to throw an unexpected error + doThrow(new RuntimeException("Unexpected error")) + .when(tokenConfigurationParameter) + .getPublicKey(); + + // When & Then + assertThatThrownBy(() -> tokenService.verifyAndValidate(token)) + .isInstanceOf(ResponseStatusException.class) + .hasMessageContaining("Error validating token") + .hasCauseInstanceOf(RuntimeException.class); + } + + @Test + void givenExpiredToken_whenVerifyAndValidate_thenThrowJwtException() { + + // Given + String expiredToken = Jwts.builder() + .claim("user_id", "12345") + .issuedAt(new Date(System.currentTimeMillis() - 86400000L)) // 1 day ago + .expiration(new Date(System.currentTimeMillis() - 43200000L)) // 12 hours ago + .signWith(keyPair.getPrivate()) + .compact(); + + + // When & Then + assertThatThrownBy(() -> tokenService.verifyAndValidate(expiredToken)) + .isInstanceOf(ResponseStatusException.class) + .hasMessageContaining("Token has expired") + .hasCauseInstanceOf(JwtException.class); + + } + + +} \ No newline at end of file diff --git a/userservice/src/test/java/com/springbootmicroservices/userservice/utils/KeyConverterTest.java b/userservice/src/test/java/com/springbootmicroservices/userservice/utils/KeyConverterTest.java new file mode 100644 index 0000000..62084b5 --- /dev/null +++ b/userservice/src/test/java/com/springbootmicroservices/userservice/utils/KeyConverterTest.java @@ -0,0 +1,62 @@ +package com.springbootmicroservices.userservice.utils; + +import org.bouncycastle.openssl.PEMException; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.InvocationTargetException; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.*; + +class KeyConverterTest { + + @Test + void utilityClass_ShouldNotBeInstantiated() { + assertThrows(InvocationTargetException.class, () -> { + // Attempt to use reflection to create an instance of the utility class + java.lang.reflect.Constructor constructor = KeyConverter.class.getDeclaredConstructor(); + constructor.setAccessible(true); + constructor.newInstance(); + }); + } + + + @Test + void givenEmptyPublicKey_whenConvertPublicKey_thenThrowRuntimeException() { + // Given + String emptyPublicPemKey = ""; + + // When & Then + assertThatThrownBy(() -> KeyConverter.convertPublicKey(emptyPublicPemKey)) + .isInstanceOf(RuntimeException.class) + .hasCauseInstanceOf(PEMException.class) + .hasMessageContaining("PEMException"); + } + + @Test + void givenMalformedPrivateKey_whenConvertPrivateKey_thenThrowRuntimeException() { + // Given + String malformedPrivatePemKey = "-----BEGIN PRIVATE KEY-----\n" + + "malformedkey\n" + + "-----END PRIVATE KEY-----"; + + // When & Then + assertThatThrownBy(() -> KeyConverter.convertPrivateKey(malformedPrivatePemKey)) + .isInstanceOf(RuntimeException.class) + .hasCauseInstanceOf(PEMException.class) + .hasMessageContaining("PEMException"); + } + + @Test + void givenEmptyPrivateKey_whenConvertPrivateKey_thenThrowRuntimeException() { + // Given + String emptyPrivatePemKey = ""; + + // When & Then + assertThatThrownBy(() -> KeyConverter.convertPrivateKey(emptyPrivatePemKey)) + .isInstanceOf(RuntimeException.class) + .hasCauseInstanceOf(PEMException.class) + .hasMessageContaining("PEMException"); + } + +} \ No newline at end of file