From 20ad9d7a39fd9d20f5f44049096305ca87ada2d8 Mon Sep 17 00:00:00 2001 From: AnonimProgrammer Date: Mon, 20 Oct 2025 14:15:24 +0400 Subject: [PATCH] Updates. --- .../security/config/SystemConfiguration.java | 13 ++- .../security/controller/AuthController.java | 11 +- .../exception/InvalidTokenException.java | 13 +++ .../handler/GlobalExceptionHandler.java | 13 ++- .../security/service/cache/CacheService.java | 2 + .../service/cache/impl/CacheServiceImpl.java | 25 +++++ .../security/service/security/JwtService.java | 6 ++ .../security/UserAuthenticationService.java | 4 +- .../service/security/impl/JwtServiceImpl.java | 36 +++++++ .../impl/UserAuthenticationServiceImpl.java | 18 +++- .../security/mapper/CredentialMapperTest.java | 57 ++++++++++ .../security/mapper/UserMapperTest.java | 100 ++++++++++++++++++ .../security/impl/JwtServiceImplTest.java | 46 ++++++++ .../UserAuthenticationServiceImplTest.java | 95 ++++++++++++++--- 14 files changed, 408 insertions(+), 31 deletions(-) create mode 100644 src/main/java/com/micropay/security/exception/InvalidTokenException.java create mode 100644 src/test/java/com/micropay/security/mapper/CredentialMapperTest.java create mode 100644 src/test/java/com/micropay/security/mapper/UserMapperTest.java diff --git a/src/main/java/com/micropay/security/config/SystemConfiguration.java b/src/main/java/com/micropay/security/config/SystemConfiguration.java index 7642c93..af350de 100644 --- a/src/main/java/com/micropay/security/config/SystemConfiguration.java +++ b/src/main/java/com/micropay/security/config/SystemConfiguration.java @@ -13,12 +13,14 @@ import org.springframework.data.redis.serializer.RedisSerializationContext; import java.time.Duration; +import java.util.HashMap; +import java.util.Map; @EnableCaching @Configuration public class SystemConfiguration { - public final static String WALLET_SERVICE_URL = "http://localhost:8110/v1/internal/wallets"; + public final static String WALLET_SERVICE_URL = "http://wallet-service:8110/v1/internal/wallets"; @Bean public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) { @@ -27,13 +29,18 @@ public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer(objectMapper); - RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() + RedisCacheConfiguration defaultConfig = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofSeconds(60)) .serializeValuesWith( RedisSerializationContext.SerializationPair.fromSerializer(serializer) ); + Map cacheConfigurations = new HashMap<>(); + cacheConfigurations.put("blacklistedTokens", + defaultConfig.entryTtl(Duration.ofDays(7)) + ); return RedisCacheManager.builder(connectionFactory) - .cacheDefaults(config) + .cacheDefaults(defaultConfig) + .withInitialCacheConfigurations(cacheConfigurations) .build(); } } diff --git a/src/main/java/com/micropay/security/controller/AuthController.java b/src/main/java/com/micropay/security/controller/AuthController.java index 456e32c..33de8c9 100644 --- a/src/main/java/com/micropay/security/controller/AuthController.java +++ b/src/main/java/com/micropay/security/controller/AuthController.java @@ -8,14 +8,12 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import java.util.UUID; - @RestController @RequestMapping("/v1/auth") @RequiredArgsConstructor public class AuthController { - private final UserAuthenticationService userAuthenticationService ; + private final UserAuthenticationService userAuthenticationService; @PostMapping("/register") public ResponseEntity register(@Valid @RequestBody RegisterRequest registerRequest) { @@ -25,9 +23,10 @@ public ResponseEntity register(@Valid @RequestBody RegisterRequest } @PostMapping("/refresh-access-token") - public ResponseEntity refreshAccessToken(@RequestHeader ("X-User-Id") UUID userId) { - AuthResponse response = userAuthenticationService - .refreshAccessToken(userId); + public ResponseEntity refreshAccessToken( + @RequestHeader ("X-Refresh-Token") String refreshToken + ) { + AuthResponse response = userAuthenticationService.refreshAccessToken(refreshToken); return ResponseEntity.ok(response); } diff --git a/src/main/java/com/micropay/security/exception/InvalidTokenException.java b/src/main/java/com/micropay/security/exception/InvalidTokenException.java new file mode 100644 index 0000000..931ba96 --- /dev/null +++ b/src/main/java/com/micropay/security/exception/InvalidTokenException.java @@ -0,0 +1,13 @@ +package com.micropay.security.exception; + +public class InvalidTokenException extends RuntimeException { + + public InvalidTokenException() { + super("Invalid token."); + } + + public InvalidTokenException(String message) { + super(message); + } + +} diff --git a/src/main/java/com/micropay/security/exception/handler/GlobalExceptionHandler.java b/src/main/java/com/micropay/security/exception/handler/GlobalExceptionHandler.java index ae5afb4..00de9ba 100644 --- a/src/main/java/com/micropay/security/exception/handler/GlobalExceptionHandler.java +++ b/src/main/java/com/micropay/security/exception/handler/GlobalExceptionHandler.java @@ -1,6 +1,7 @@ package com.micropay.security.exception.handler; import com.micropay.security.exception.DuplicateObjectException; +import com.micropay.security.exception.InvalidTokenException; import com.micropay.security.exception.NotActiveUserException; import com.micropay.security.exception.UserNotFoundException; import org.springframework.http.HttpStatus; @@ -53,7 +54,7 @@ public ResponseEntity handleNotActiveUserException(NotActiveUserE "User is blocked or suspended.", LocalDateTime.now() ); - return ResponseEntity.status(HttpStatus.FORBIDDEN).body(body); + return ResponseEntity.status(HttpStatus.CONFLICT).body(body); } @ExceptionHandler(MethodArgumentNotValidException.class) @@ -67,4 +68,14 @@ public ResponseEntity handleMethodArgumentNotValidException(Metho return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(body); } + @ExceptionHandler(InvalidTokenException.class) + public ResponseEntity handleInvalidTokenException(InvalidTokenException exception) { + ErrorResponse body = new ErrorResponse( + HttpStatus.UNAUTHORIZED.value(), + exception.getMessage(), + LocalDateTime.now() + ); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(body); + } + } diff --git a/src/main/java/com/micropay/security/service/cache/CacheService.java b/src/main/java/com/micropay/security/service/cache/CacheService.java index 78ad127..7705af0 100644 --- a/src/main/java/com/micropay/security/service/cache/CacheService.java +++ b/src/main/java/com/micropay/security/service/cache/CacheService.java @@ -8,6 +8,8 @@ public interface CacheService { T getOrPut(String cacheName, String key, TypeReference type, Supplier supplier); + void checkAndBlacklist(String refreshToken); + void evictAll(String cacheName); void evict(String cacheName, String key); diff --git a/src/main/java/com/micropay/security/service/cache/impl/CacheServiceImpl.java b/src/main/java/com/micropay/security/service/cache/impl/CacheServiceImpl.java index 038d65e..7251479 100644 --- a/src/main/java/com/micropay/security/service/cache/impl/CacheServiceImpl.java +++ b/src/main/java/com/micropay/security/service/cache/impl/CacheServiceImpl.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; +import com.micropay.security.exception.InvalidTokenException; import com.micropay.security.service.cache.CacheService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -44,6 +45,30 @@ public T getOrPut(String cacheName, String key, TypeReference type, Suppl return value; } + private Cache getCache() { + Cache cache = cacheManager.getCache("blacklistedTokens"); + if (cache == null) { + log.warn("Cache '{}' not found. ", "blacklistedTokens"); + } + return cache; + } + + @Override + public void checkAndBlacklist(String refreshToken) { + Cache cache = getCache(); + String key = generateKey(refreshToken); + + if (cache.get(key) != null) { + throw new InvalidTokenException("Token blacklisted."); + } + cache.put(key, true); + log.info("Token blacklisted: {} ", refreshToken); + } + + private String generateKey(String token) { + return "blacklist:" + token; + } + @Override public void evict(String cacheName, String key) { Cache cache = cacheManager.getCache(cacheName); diff --git a/src/main/java/com/micropay/security/service/security/JwtService.java b/src/main/java/com/micropay/security/service/security/JwtService.java index a22c6f2..b5d04ce 100644 --- a/src/main/java/com/micropay/security/service/security/JwtService.java +++ b/src/main/java/com/micropay/security/service/security/JwtService.java @@ -7,4 +7,10 @@ public interface JwtService { AuthResponse generateTokens(User user); + void validateToken(String token); + + String extractUserId(String token); + + String extractRole(String token); + } diff --git a/src/main/java/com/micropay/security/service/security/UserAuthenticationService.java b/src/main/java/com/micropay/security/service/security/UserAuthenticationService.java index 72d701a..6214cd0 100644 --- a/src/main/java/com/micropay/security/service/security/UserAuthenticationService.java +++ b/src/main/java/com/micropay/security/service/security/UserAuthenticationService.java @@ -4,12 +4,10 @@ import com.micropay.security.dto.response.AuthResponse; import org.springframework.security.core.userdetails.UserDetailsService; -import java.util.UUID; - public interface UserAuthenticationService extends UserDetailsService { AuthResponse registerUser(RegisterRequest registerRequest); - AuthResponse refreshAccessToken(UUID userId); + AuthResponse refreshAccessToken(String refreshToken); } diff --git a/src/main/java/com/micropay/security/service/security/impl/JwtServiceImpl.java b/src/main/java/com/micropay/security/service/security/impl/JwtServiceImpl.java index 4126550..84abbdc 100644 --- a/src/main/java/com/micropay/security/service/security/impl/JwtServiceImpl.java +++ b/src/main/java/com/micropay/security/service/security/impl/JwtServiceImpl.java @@ -1,9 +1,11 @@ package com.micropay.security.service.security.impl; import com.micropay.security.dto.response.AuthResponse; +import com.micropay.security.exception.InvalidTokenException; import com.micropay.security.model.RoleType; import com.micropay.security.model.entity.User; import com.micropay.security.service.security.JwtService; +import io.jsonwebtoken.JwtException; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.security.Keys; @@ -39,6 +41,40 @@ public AuthResponse generateTokens(User user) { return new AuthResponse(accessToken, refreshToken); } + @Override + public void validateToken(String token) { + try { + Jwts.parser().verifyWith(generateSecretKey(secretKey)).build().parseSignedClaims(token); + } catch (JwtException exception) { + throw new InvalidTokenException(); + } + } + + @Override + public String extractUserId(String token) { + return Jwts.parser() + .verifyWith(generateSecretKey(secretKey)) + .build() + .parseSignedClaims(token) + .getPayload() + .getSubject(); + } + + @Override + public String extractRole(String token) { + return Jwts.parser() + .verifyWith(generateSecretKey(secretKey)) + .build() + .parseSignedClaims(token) + .getPayload() + .get("role", String.class); + } + + private SecretKey generateSecretKey(String secretKey) { + byte[] keyBytes = Decoders.BASE64.decode(secretKey); + return Keys.hmacShaKeyFor(keyBytes); + } + private String generateAccessToken(UUID userId, RoleType role) { return Jwts.builder() .subject(userId.toString()) diff --git a/src/main/java/com/micropay/security/service/security/impl/UserAuthenticationServiceImpl.java b/src/main/java/com/micropay/security/service/security/impl/UserAuthenticationServiceImpl.java index e22ede7..cead4b4 100644 --- a/src/main/java/com/micropay/security/service/security/impl/UserAuthenticationServiceImpl.java +++ b/src/main/java/com/micropay/security/service/security/impl/UserAuthenticationServiceImpl.java @@ -87,16 +87,30 @@ public UserDetails loadUserByUsername(String phoneNumber) throws UsernameNotFoun } @Override - public AuthResponse refreshAccessToken(UUID userId) { - log.info("Refreshing access token for user: {}", userId); + public AuthResponse refreshAccessToken(String refreshToken) { + cacheService.checkAndBlacklist(refreshToken); + log.info("Refreshing access token."); + jwtService.validateToken(refreshToken); + + UUID userId = UUID.fromString(jwtService.extractUserId(refreshToken)); User user = userRepository.findById(userId) .orElseThrow(() -> new UserNotFoundException(userId)); + String role = jwtService.extractRole(refreshToken); + validateRole(role, user); + isActive(user); return jwtService.generateTokens(user); } + private void validateRole(String role, User user) { + String userRole = user.getRole().getRole().toString(); + if (!userRole.equals(role)) { + throw new InvalidTokenException("Invalid refresh token."); + } + } + private void isActive(User user) { if (user.getStatus() != UserStatus.ACTIVE) { throw new NotActiveUserException(user.getId()); diff --git a/src/test/java/com/micropay/security/mapper/CredentialMapperTest.java b/src/test/java/com/micropay/security/mapper/CredentialMapperTest.java new file mode 100644 index 0000000..2329434 --- /dev/null +++ b/src/test/java/com/micropay/security/mapper/CredentialMapperTest.java @@ -0,0 +1,57 @@ +package com.micropay.security.mapper; + +import com.micropay.security.model.entity.Credential; +import com.micropay.security.model.entity.User; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class CredentialMapperTest { + + private CredentialMapper mapper; + private final static String PIN_HASH = "PIN_HASH"; + + @BeforeEach + void setUp() { + mapper = new CredentialMapperImpl(); + } + + @Test + void shouldBuildCredentialEntity_WhenUserAndPinHashProvided() { + User user = new User(); + + Credential credential = mapper.buildEntity(user, PIN_HASH); + + assertNotNull(credential); + assertEquals(user, credential.getUser()); + assertEquals(PIN_HASH, credential.getPinHash()); + } + + @Test + void shouldBuildCredentialEntity_WhenUserIsNullButPinHashProvided() { + Credential credential = mapper.buildEntity(null, PIN_HASH); + + assertNotNull(credential); + assertNull(credential.getUser()); + assertEquals(PIN_HASH, credential.getPinHash()); + } + + @Test + void shouldBuildCredentialEntity_WhenUserProvidedButPinHashIsNull() { + User user = new User(); + + Credential credential = mapper.buildEntity(user, null); + + assertNotNull(credential); + assertEquals(user, credential.getUser()); + assertNull(credential.getPinHash()); + } + + @Test + void shouldReturnNull_WhenUserAndPinHashAreNull() { + Credential credential = mapper.buildEntity(null, null); + + assertNull(credential); + } +} diff --git a/src/test/java/com/micropay/security/mapper/UserMapperTest.java b/src/test/java/com/micropay/security/mapper/UserMapperTest.java new file mode 100644 index 0000000..fec3588 --- /dev/null +++ b/src/test/java/com/micropay/security/mapper/UserMapperTest.java @@ -0,0 +1,100 @@ +package com.micropay.security.mapper; + +import com.micropay.security.dto.request.RegisterRequest; +import com.micropay.security.dto.response.UserResponse; +import com.micropay.security.model.RoleType; +import com.micropay.security.model.UserModel; +import com.micropay.security.model.UserStatus; +import com.micropay.security.model.entity.Role; +import com.micropay.security.model.entity.User; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class UserMapperTest { + + private UserMapper mapper; + + @BeforeEach + void setUp() { + mapper = new UserMapperImpl(); + } + + @Test + void shouldMapUserEntityToUserModel() { + Role role = new Role(); + role.setRole(RoleType.ADMIN); + + User user = new User(); + user.setRole(role); + user.setPhoneNumber("phoneNumber"); + user.setFullName("Omar Ismailov"); + user.setEmail("omar@icloud.com"); + user.setStatus(UserStatus.ACTIVE); + + UserModel model = mapper.toModel(user); + + assertNotNull(model); + assertEquals(RoleType.ADMIN, model.getRole()); + assertEquals("phoneNumber", model.getPhoneNumber()); + assertEquals("Omar Ismailov", model.getFullName()); + assertEquals("omar@icloud.com", model.getEmail()); + } + + @Test + void shouldReturnNull_WhenUserEntityIsNull() { + assertNull(mapper.toModel(null)); + } + + @Test + void shouldBuildUserEntityFromRegisterRequestAndRole() { + RegisterRequest request = new RegisterRequest( + "phoneNumber", "Omar Ismailov", "omar@icloud.com", "111111" + ); + Role role = new Role(); + role.setRole(RoleType.USER); + + User user = mapper.buildEntity(request, role); + + assertNotNull(user); + assertEquals("Omar Ismailov", user.getFullName()); + assertEquals("phoneNumber", user.getPhoneNumber()); + assertEquals("omar@icloud.com", user.getEmail()); + assertEquals(role, user.getRole()); + } + + @Test + void shouldHandleNullsInBuildEntity() { + Role role = new Role(); + role.setRole(RoleType.USER); + + User user = mapper.buildEntity(null, role); + assertNotNull(user); + assertEquals(role, user.getRole()); + assertNull(user.getEmail()); + assertNull(user.getFullName()); + + assertNull(mapper.buildEntity(null, null)); + } + + @Test + void shouldMapUserToUserResponse() { + User user = new User(); + user.setPhoneNumber("phoneNumber"); + user.setFullName("Omar Ismailov"); + user.setEmail("omar@icloud.com"); + + UserResponse response = mapper.toResponse(user); + + assertNotNull(response); + assertEquals("phoneNumber", response.phoneNumber()); + assertEquals("Omar Ismailov", response.fullName()); + assertEquals("omar@icloud.com", response.email()); + } + + @Test + void shouldReturnNull_WhenUserIsNullInToResponse() { + assertNull(mapper.toResponse(null)); + } +} diff --git a/src/test/java/com/micropay/security/service/security/impl/JwtServiceImplTest.java b/src/test/java/com/micropay/security/service/security/impl/JwtServiceImplTest.java index f5e869c..a3b5c14 100644 --- a/src/test/java/com/micropay/security/service/security/impl/JwtServiceImplTest.java +++ b/src/test/java/com/micropay/security/service/security/impl/JwtServiceImplTest.java @@ -75,6 +75,52 @@ void refreshToken_ShouldHaveLaterExpirationThanAccessToken() { assertTrue(refreshClaims.getExpiration().after(accessClaims.getExpiration())); } + @Test + void validateToken_ShouldNotThrowForValidToken() { + AuthResponse response = jwtService.generateTokens(user); + + assertDoesNotThrow(() -> jwtService.validateToken(response.accessToken())); + } + + @Test + void validateToken_ShouldThrowForInvalidToken() { + String invalidToken = "invalid.token.value"; + + assertThrows(com.micropay.security.exception.InvalidTokenException.class, + () -> jwtService.validateToken(invalidToken)); + } + + @Test + void extractUserId_ShouldReturnCorrectUserId() { + AuthResponse response = jwtService.generateTokens(user); + String extractedId = jwtService.extractUserId(response.accessToken()); + + assertEquals(user.getId().toString(), extractedId); + } + + @Test + void extractRole_ShouldReturnCorrectRole() { + AuthResponse response = jwtService.generateTokens(user); + String extractedRole = jwtService.extractRole(response.accessToken()); + + assertEquals(user.getRole().getRole().name(), extractedRole); + } + + @Test + void validateToken_ShouldThrowForExpiredToken() { + SecretKey key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(SECRET)); + String expiredToken = Jwts.builder() + .subject(user.getId().toString()) + .claim("role", user.getRole().getRole().name()) + .issuedAt(new Date(System.currentTimeMillis() - 10_000)) + .expiration(new Date(System.currentTimeMillis() - 5_000)) + .signWith(key) + .compact(); + + assertThrows(com.micropay.security.exception.InvalidTokenException.class, + () -> jwtService.validateToken(expiredToken)); + } + private Claims extractClaims(String token) { SecretKey key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(SECRET)); return Jwts.parser() diff --git a/src/test/java/com/micropay/security/service/security/impl/UserAuthenticationServiceImplTest.java b/src/test/java/com/micropay/security/service/security/impl/UserAuthenticationServiceImplTest.java index 9c5bd37..5d42ab4 100644 --- a/src/test/java/com/micropay/security/service/security/impl/UserAuthenticationServiceImplTest.java +++ b/src/test/java/com/micropay/security/service/security/impl/UserAuthenticationServiceImplTest.java @@ -6,6 +6,7 @@ import com.micropay.security.mapper.CredentialMapper; import com.micropay.security.mapper.UserMapper; import com.micropay.security.model.CustomUserDetails; +import com.micropay.security.model.RoleType; import com.micropay.security.model.UserStatus; import com.micropay.security.model.entity.Credential; import com.micropay.security.model.entity.Role; @@ -42,7 +43,8 @@ class UserAuthenticationServiceImplTest { private User user; private Role role; private Credential credential; - private final UUID USER_ID = UUID.randomUUID(); + private final static UUID USER_ID = UUID.randomUUID(); + private final static String REFRESH_TOKEN = "sample-refresh-token"; @BeforeEach void setUp() { @@ -77,6 +79,8 @@ void setUp() { } user.setStatus(UserStatus.ACTIVE); role = new Role(); + role.setRole(RoleType.USER); + user.setRole(role); credential = new Credential(); } @@ -160,30 +164,89 @@ void loadUserByUsername_ShouldThrow_WhenUserNotActive() { } @Test - void refreshAccessToken_ShouldReturnNewTokens_WhenUserActive() { - user.setStatus(UserStatus.ACTIVE); - when(userRepository.findById(USER_ID)).thenReturn(Optional.of(user)); - when(jwtService.generateTokens(user)).thenReturn( - new AuthResponse("newAccess", "newRefresh") - ); + void shouldRefreshAccessTokenSuccessfully() { + UUID userId = user.getId(); + + doNothing().when(cacheService).checkAndBlacklist(REFRESH_TOKEN); + doNothing().when(jwtService).validateToken(REFRESH_TOKEN); + when(jwtService.extractUserId(REFRESH_TOKEN)).thenReturn(userId.toString()); + when(jwtService.extractRole(REFRESH_TOKEN)).thenReturn("USER"); + when(userRepository.findById(userId)).thenReturn(Optional.of(user)); + + AuthResponse expectedResponse = new AuthResponse("newAccessToken", "newREFRESH_TOKEN"); + when(jwtService.generateTokens(user)).thenReturn(expectedResponse); + + AuthResponse actual = service.refreshAccessToken(REFRESH_TOKEN); + + assertEquals(expectedResponse, actual); + verify(cacheService).checkAndBlacklist(REFRESH_TOKEN); + verify(jwtService).validateToken(REFRESH_TOKEN); + verify(jwtService).extractUserId(REFRESH_TOKEN); + verify(jwtService).extractRole(REFRESH_TOKEN); + verify(userRepository).findById(userId); + verify(jwtService).generateTokens(user); + } - AuthResponse response = service.refreshAccessToken(USER_ID); + @Test + void shouldThrowInvalidTokenExceptionWhenREFRESH_TOKENBlacklisted() { + doThrow(new InvalidTokenException("Token blacklisted")) + .when(cacheService).checkAndBlacklist(REFRESH_TOKEN); - assertEquals("newAccess", response.accessToken()); - verify(jwtService).generateTokens(user); + InvalidTokenException exception = assertThrows( + InvalidTokenException.class, () -> service.refreshAccessToken(REFRESH_TOKEN) + ); + assertEquals("Token blacklisted", exception.getMessage()); + verify(cacheService).checkAndBlacklist(REFRESH_TOKEN); + verifyNoMoreInteractions(jwtService, userRepository); } @Test - void refreshAccessToken_ShouldThrow_WhenUserNotFound() { - when(userRepository.findById(USER_ID)).thenReturn(Optional.empty()); - assertThrows(UserNotFoundException.class, () -> service.refreshAccessToken(USER_ID)); + void shouldThrowInvalidTokenExceptionWhenRoleMismatch() { + UUID userId = user.getId(); + + doNothing().when(cacheService).checkAndBlacklist(REFRESH_TOKEN); + doNothing().when(jwtService).validateToken(REFRESH_TOKEN); + when(jwtService.extractUserId(REFRESH_TOKEN)).thenReturn(userId.toString()); + when(jwtService.extractRole(REFRESH_TOKEN)).thenReturn("ADMIN"); + when(userRepository.findById(userId)).thenReturn(Optional.of(user)); + + InvalidTokenException ex = assertThrows( + InvalidTokenException.class, () -> service.refreshAccessToken(REFRESH_TOKEN) + ); + assertEquals("Invalid refresh token.", ex.getMessage()); } @Test - void refreshAccessToken_ShouldThrow_WhenUserNotActive() { + void shouldThrowNotActiveUserExceptionWhenUserNotActive() { + UUID userId = user.getId(); user.setStatus(UserStatus.BLOCKED); - when(userRepository.findById(USER_ID)).thenReturn(Optional.of(user)); - assertThrows(NotActiveUserException.class, () -> service.refreshAccessToken(USER_ID)); + doNothing().when(cacheService).checkAndBlacklist(REFRESH_TOKEN); + doNothing().when(jwtService).validateToken(REFRESH_TOKEN); + when(jwtService.extractUserId(REFRESH_TOKEN)).thenReturn(userId.toString()); + when(jwtService.extractRole(REFRESH_TOKEN)).thenReturn("USER"); + when(userRepository.findById(userId)).thenReturn(Optional.of(user)); + + NotActiveUserException ex = assertThrows( + NotActiveUserException.class, () -> service.refreshAccessToken(REFRESH_TOKEN) + ); + assertTrue(ex.getMessage().contains(userId.toString())); + } + + @Test + void shouldThrowUserNotFoundExceptionWhenUserDoesNotExist() { + UUID userId = UUID.randomUUID(); + + doNothing().when(cacheService).checkAndBlacklist(REFRESH_TOKEN); + doNothing().when(jwtService).validateToken(REFRESH_TOKEN); + when(jwtService.extractUserId(REFRESH_TOKEN)).thenReturn(userId.toString()); + when(userRepository.findById(userId)).thenReturn(Optional.empty()); + + UserNotFoundException ex = assertThrows( + UserNotFoundException.class, () -> service.refreshAccessToken(REFRESH_TOKEN) + ); + + assertEquals("User not found with id: " + userId, ex.getMessage()); } + }