diff --git a/auth-service/pom.xml b/auth-service/pom.xml
index 1fb5c28..351b279 100644
--- a/auth-service/pom.xml
+++ b/auth-service/pom.xml
@@ -68,7 +68,7 @@
runtime
true
-
+
org.springframework.boot
spring-boot-starter-test
@@ -177,6 +177,18 @@
org.springframework.boot
spring-boot-maven-plugin
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
diff --git a/auth-service/src/main/java/com/techtorque/auth_service/controller/AuthController.java b/auth-service/src/main/java/com/techtorque/auth_service/controller/AuthController.java
index 22f5952..2a822a1 100644
--- a/auth-service/src/main/java/com/techtorque/auth_service/controller/AuthController.java
+++ b/auth-service/src/main/java/com/techtorque/auth_service/controller/AuthController.java
@@ -23,209 +23,183 @@
* Handles login, registration, and health check requests
*/
@RestController
-// Class-level request mapping removed — gateway strips prefixes before forwarding
+// Class-level request mapping removed — gateway strips prefixes before
+// forwarding
// @RequestMapping("/api/v1/auth")
// CORS handled at the API Gateway; remove @CrossOrigin to avoid conflicts
// @CrossOrigin(origins = "*", maxAge = 3600)
@Tag(name = "Authentication", description = "Authentication and user management endpoints")
public class AuthController {
-
+
@Autowired
private AuthService authService;
-
+
// --- NEW DEPENDENCY ---
// We need UserService to call the createEmployee method
@Autowired
private UserService userService;
-
+
/**
* User login endpoint
+ *
* @param loginRequest Login credentials
* @return JWT token and user details
*/
- @Operation(
- summary = "User Login",
- description = "Authenticate user with username/email and password. Returns JWT token on success. Rate limited to prevent brute force attacks."
- )
+ @Operation(summary = "User Login", description = "Authenticate user with username/email and password. Returns JWT token on success. Rate limited to prevent brute force attacks.")
@ApiResponses(value = {
- @ApiResponse(responseCode = "200", description = "Login successful, JWT token returned"),
- @ApiResponse(responseCode = "401", description = "Invalid credentials or account locked"),
- @ApiResponse(responseCode = "400", description = "Invalid request format")
+ @ApiResponse(responseCode = "200", description = "Login successful, JWT token returned"),
+ @ApiResponse(responseCode = "401", description = "Invalid credentials or account locked"),
+ @ApiResponse(responseCode = "400", description = "Invalid request format")
})
@PostMapping("/login")
- public ResponseEntity> authenticateUser(@Valid @RequestBody LoginRequest loginRequest, HttpServletRequest request) {
+ public ResponseEntity> authenticateUser(@Valid @RequestBody LoginRequest loginRequest,
+ HttpServletRequest request) {
LoginResponse loginResponse = authService.authenticateUser(loginRequest, request);
return ResponseEntity.ok(loginResponse);
}
-
+
/**
* User registration endpoint
+ *
* @param registerRequest Registration details
* @return Success message
*/
- @Operation(
- summary = "Register New User",
- description = "Register a new customer account. Email verification is required before login."
- )
+ @Operation(summary = "Register New User", description = "Register a new customer account. Email verification is required before login.")
@ApiResponses(value = {
- @ApiResponse(responseCode = "201", description = "Registration successful, verification email sent"),
- @ApiResponse(responseCode = "400", description = "Invalid request or username/email already exists")
+ @ApiResponse(responseCode = "201", description = "Registration successful, verification email sent"),
+ @ApiResponse(responseCode = "400", description = "Invalid request or username/email already exists")
})
@PostMapping("/register")
public ResponseEntity> registerUser(@Valid @RequestBody RegisterRequest registerRequest) {
String message = authService.registerUser(registerRequest);
return ResponseEntity.status(HttpStatus.CREATED).body(ApiSuccess.of(message));
}
-
+
/**
* Verify email with token
*/
- @Operation(
- summary = "Verify Email",
- description = "Verify user email address with token sent via email. Returns JWT tokens on success."
- )
+ @Operation(summary = "Verify Email", description = "Verify user email address with token sent via email. Returns JWT tokens on success.")
@ApiResponses(value = {
- @ApiResponse(responseCode = "200", description = "Email verified successfully, user logged in"),
- @ApiResponse(responseCode = "400", description = "Invalid, expired, or already used token")
+ @ApiResponse(responseCode = "200", description = "Email verified successfully, user logged in"),
+ @ApiResponse(responseCode = "400", description = "Invalid, expired, or already used token")
})
@PostMapping("/verify-email")
- public ResponseEntity> verifyEmail(@Valid @RequestBody VerifyEmailRequest request, HttpServletRequest httpRequest) {
+ public ResponseEntity> verifyEmail(@Valid @RequestBody VerifyEmailRequest request,
+ HttpServletRequest httpRequest) {
LoginResponse response = authService.verifyEmail(request.getToken(), httpRequest);
return ResponseEntity.ok(response);
}
-
+
/**
* Resend verification email
*/
- @Operation(
- summary = "Resend Verification Email",
- description = "Resend verification email to the specified address"
- )
+ @Operation(summary = "Resend Verification Email", description = "Resend verification email to the specified address")
@ApiResponses(value = {
- @ApiResponse(responseCode = "200", description = "Verification email sent successfully"),
- @ApiResponse(responseCode = "400", description = "Email not found or already verified")
+ @ApiResponse(responseCode = "200", description = "Verification email sent successfully"),
+ @ApiResponse(responseCode = "400", description = "Email not found or already verified")
})
@PostMapping("/resend-verification")
public ResponseEntity> resendVerification(@Valid @RequestBody ResendVerificationRequest request) {
String message = authService.resendVerificationEmail(request.getEmail());
return ResponseEntity.ok(ApiSuccess.of(message));
}
-
+
/**
* Refresh JWT token
*/
- @Operation(
- summary = "Refresh Access Token",
- description = "Get a new access token using a valid refresh token"
- )
+ @Operation(summary = "Refresh Access Token", description = "Get a new access token using a valid refresh token")
@ApiResponses(value = {
- @ApiResponse(responseCode = "200", description = "New access token generated"),
- @ApiResponse(responseCode = "401", description = "Invalid, expired, or revoked refresh token")
+ @ApiResponse(responseCode = "200", description = "New access token generated"),
+ @ApiResponse(responseCode = "401", description = "Invalid, expired, or revoked refresh token")
})
@PostMapping("/refresh")
public ResponseEntity> refreshToken(@Valid @RequestBody RefreshTokenRequest request) {
LoginResponse response = authService.refreshToken(request.getRefreshToken());
return ResponseEntity.ok(response);
}
-
+
/**
* Logout endpoint
*/
- @Operation(
- summary = "Logout User",
- description = "Logout user and revoke refresh token",
- security = @SecurityRequirement(name = "bearerAuth")
- )
+ @Operation(summary = "Logout User", description = "Logout user and revoke refresh token", security = @SecurityRequirement(name = "bearerAuth"))
@ApiResponses(value = {
- @ApiResponse(responseCode = "200", description = "Logged out successfully"),
- @ApiResponse(responseCode = "400", description = "Invalid refresh token")
+ @ApiResponse(responseCode = "200", description = "Logged out successfully"),
+ @ApiResponse(responseCode = "400", description = "Invalid refresh token")
})
@PostMapping("/logout")
public ResponseEntity> logout(@Valid @RequestBody LogoutRequest request) {
authService.logout(request.getRefreshToken());
return ResponseEntity.ok(ApiSuccess.of("Logged out successfully"));
}
-
+
/**
* Forgot password - request reset
*/
- @Operation(
- summary = "Forgot Password",
- description = "Request password reset email"
- )
+ @Operation(summary = "Forgot Password", description = "Request password reset email")
@ApiResponses(value = {
- @ApiResponse(responseCode = "200", description = "Password reset email sent"),
- @ApiResponse(responseCode = "404", description = "Email not found")
+ @ApiResponse(responseCode = "200", description = "Password reset email sent"),
+ @ApiResponse(responseCode = "404", description = "Email not found")
})
@PostMapping("/forgot-password")
public ResponseEntity> forgotPassword(@Valid @RequestBody ForgotPasswordRequest request) {
String message = authService.forgotPassword(request.getEmail());
return ResponseEntity.ok(ApiSuccess.of(message));
}
-
+
/**
* Reset password with token
*/
- @Operation(
- summary = "Reset Password",
- description = "Reset password using token from email"
- )
+ @Operation(summary = "Reset Password", description = "Reset password using token from email")
@ApiResponses(value = {
- @ApiResponse(responseCode = "200", description = "Password reset successfully"),
- @ApiResponse(responseCode = "400", description = "Invalid, expired, or already used token")
+ @ApiResponse(responseCode = "200", description = "Password reset successfully"),
+ @ApiResponse(responseCode = "400", description = "Invalid, expired, or already used token")
})
@PostMapping("/reset-password")
public ResponseEntity> resetPassword(@Valid @RequestBody ResetPasswordWithTokenRequest request) {
String message = authService.resetPassword(request.getToken(), request.getNewPassword());
return ResponseEntity.ok(ApiSuccess.of(message));
}
-
+
/**
* Change password (authenticated users)
* Note: This endpoint moved to UserController as /users/me/change-password
* Keeping for backwards compatibility
*/
- @Operation(
- summary = "Change Password",
- description = "Change password for authenticated user. Use current password for verification.",
- security = @SecurityRequirement(name = "bearerAuth")
- )
+ @Operation(summary = "Change Password", description = "Change password for authenticated user. Use current password for verification.", security = @SecurityRequirement(name = "bearerAuth"))
@ApiResponses(value = {
- @ApiResponse(responseCode = "200", description = "Password changed successfully"),
- @ApiResponse(responseCode = "400", description = "Invalid current password"),
- @ApiResponse(responseCode = "401", description = "Authentication required")
+ @ApiResponse(responseCode = "200", description = "Password changed successfully"),
+ @ApiResponse(responseCode = "400", description = "Invalid current password"),
+ @ApiResponse(responseCode = "401", description = "Authentication required")
})
@PutMapping("/change-password")
@PreAuthorize("hasRole('CUSTOMER') or hasRole('EMPLOYEE') or hasRole('ADMIN') or hasRole('SUPER_ADMIN')")
public ResponseEntity> changePassword(@Valid @RequestBody ChangePasswordRequest changeRequest) {
try {
- Authentication authentication = org.springframework.security.core.context.SecurityContextHolder.getContext().getAuthentication();
+ Authentication authentication = org.springframework.security.core.context.SecurityContextHolder.getContext()
+ .getAuthentication();
String username = authentication.getName();
-
- userService.changeUserPassword(username, changeRequest.getCurrentPassword(), changeRequest.getNewPassword());
+
+ userService.changeUserPassword(username, changeRequest.getCurrentPassword(),
+ changeRequest.getNewPassword());
return ResponseEntity.ok(ApiSuccess.of("Password changed successfully"));
} catch (RuntimeException e) {
return ResponseEntity.badRequest().body(ApiSuccess.of("Error: " + e.getMessage()));
}
}
-
// --- NEW ENDPOINT FOR CREATING EMPLOYEES ---
/**
* ADMIN-ONLY endpoint for creating a new employee account.
+ *
* @param createEmployeeRequest DTO with username, email, and password.
* @return A success or error message.
*/
- @Operation(
- summary = "Create Employee Account",
- description = "Create a new employee account. Requires ADMIN role.",
- security = @SecurityRequirement(name = "bearerAuth")
- )
+ @Operation(summary = "Create Employee Account", description = "Create a new employee account. Requires ADMIN role.", security = @SecurityRequirement(name = "bearerAuth"))
@ApiResponses(value = {
- @ApiResponse(responseCode = "201", description = "Employee account created successfully"),
- @ApiResponse(responseCode = "400", description = "Invalid request or username already exists"),
- @ApiResponse(responseCode = "401", description = "Authentication required"),
- @ApiResponse(responseCode = "403", description = "Admin role required")
+ @ApiResponse(responseCode = "201", description = "Employee account created successfully"),
+ @ApiResponse(responseCode = "400", description = "Invalid request or username already exists"),
+ @ApiResponse(responseCode = "401", description = "Authentication required"),
+ @ApiResponse(responseCode = "403", description = "Admin role required")
})
@PostMapping("/users/employee")
@PreAuthorize("hasRole('ADMIN') or hasRole('SUPER_ADMIN')")
@@ -233,10 +207,10 @@ public ResponseEntity> createEmployee(@Valid @RequestBody CreateEmployeeReques
try {
// Now we are calling the method that was previously unused
userService.createEmployee(
- createEmployeeRequest.getUsername(),
- createEmployeeRequest.getEmail(),
- createEmployeeRequest.getPassword()
- );
+ createEmployeeRequest.getUsername(),
+ createEmployeeRequest.getEmail(),
+ createEmployeeRequest.getPassword(),
+ createEmployeeRequest.getFullName());
return ResponseEntity.status(HttpStatus.CREATED)
.body(ApiSuccess.of("Employee account created successfully!"));
} catch (RuntimeException e) {
@@ -248,6 +222,7 @@ public ResponseEntity> createEmployee(@Valid @RequestBody CreateEmployeeReques
// --- NEW ENDPOINT FOR CREATING ADMINS (SUPER_ADMIN ONLY) ---
/**
* SUPER_ADMIN-ONLY endpoint for creating a new admin account.
+ *
* @param createAdminRequest DTO with username, email, and password.
* @return A success or error message.
*/
@@ -256,28 +231,30 @@ public ResponseEntity> createEmployee(@Valid @RequestBody CreateEmployeeReques
public ResponseEntity> createAdmin(@Valid @RequestBody CreateAdminRequest createAdminRequest) {
try {
userService.createAdmin(
- createAdminRequest.getUsername(),
- createAdminRequest.getEmail(),
- createAdminRequest.getPassword()
- );
+ createAdminRequest.getUsername(),
+ createAdminRequest.getEmail(),
+ createAdminRequest.getPassword(),
+ createAdminRequest.getFullName());
return ResponseEntity.status(HttpStatus.CREATED)
.body(ApiSuccess.of("Admin account created successfully!"));
} catch (RuntimeException e) {
return ResponseEntity.badRequest().body(ApiSuccess.of("Error: " + e.getMessage()));
}
}
-
+
/**
* Health check endpoint
+ *
* @return Service status
*/
@GetMapping("/health")
public ResponseEntity> health() {
return ResponseEntity.ok(ApiSuccess.of("Authentication Service is running!"));
}
-
+
/**
* Test endpoint for authenticated users
+ *
* @return Test message
*/
@GetMapping("/test")
diff --git a/auth-service/src/main/java/com/techtorque/auth_service/controller/UserController.java b/auth-service/src/main/java/com/techtorque/auth_service/controller/UserController.java
index 9a1e837..711bf17 100644
--- a/auth-service/src/main/java/com/techtorque/auth_service/controller/UserController.java
+++ b/auth-service/src/main/java/com/techtorque/auth_service/controller/UserController.java
@@ -29,7 +29,6 @@
@RequestMapping("/users")
// CORS handled by API Gateway; remove @CrossOrigin to avoid conflicts
// @CrossOrigin(origins = "*", maxAge = 3600)
-@PreAuthorize("hasRole('ADMIN') or hasRole('SUPER_ADMIN')")
@Tag(name = "User Management", description = "User management endpoints (Admin/Super Admin only)")
@SecurityRequirement(name = "bearerAuth")
public class UserController {
@@ -44,6 +43,7 @@ public class UserController {
* Get a list of all users in the system.
*/
@GetMapping
+ @PreAuthorize("hasRole('ADMIN') or hasRole('SUPER_ADMIN')")
public ResponseEntity> getAllUsers(@RequestParam(required = false) String role) {
List users = userService.findAllUsers().stream()
.map(this::convertToDto)
@@ -56,6 +56,7 @@ public ResponseEntity> getAllUsers(@RequestParam(required = false)
* Get detailed information for a single user by their username.
*/
@GetMapping("/{username}")
+ @PreAuthorize("hasRole('ADMIN') or hasRole('SUPER_ADMIN')")
public ResponseEntity getUserByUsername(@PathVariable String username) {
return userService.findByUsername(username)
.map(user -> ResponseEntity.ok(convertToDto(user)))
@@ -66,6 +67,7 @@ public ResponseEntity getUserByUsername(@PathVariable String username)
* Disable a user's account.
*/
@PostMapping("/{username}/disable")
+ @PreAuthorize("hasRole('ADMIN') or hasRole('SUPER_ADMIN')")
public ResponseEntity> disableUser(@PathVariable String username) {
try {
userService.disableUser(username);
@@ -79,6 +81,7 @@ public ResponseEntity> disableUser(@PathVariable String username) {
* Enable a user's account.
*/
@PostMapping("/{username}/enable")
+ @PreAuthorize("hasRole('ADMIN') or hasRole('SUPER_ADMIN')")
public ResponseEntity> enableUser(@PathVariable String username) {
try {
userService.enableUser(username);
@@ -102,6 +105,7 @@ public ResponseEntity> unlockUser(@PathVariable String username) {
* Delete a user from the system permanently.
*/
@DeleteMapping("/{username}")
+ @PreAuthorize("hasRole('ADMIN') or hasRole('SUPER_ADMIN')")
public ResponseEntity> deleteUser(@PathVariable String username) {
try {
userService.deleteUser(username);
@@ -116,6 +120,7 @@ public ResponseEntity> deleteUser(@PathVariable String username) {
* PUT /api/v1/users/{username}
*/
@PutMapping("/{username}")
+ @PreAuthorize("hasRole('ADMIN') or hasRole('SUPER_ADMIN')")
public ResponseEntity> updateUser(@PathVariable String username,
@Valid @RequestBody UpdateUserRequest updateRequest) {
try {
@@ -136,6 +141,7 @@ public ResponseEntity> updateUser(@PathVariable String username,
* POST /api/v1/users/{username}/reset-password
*/
@PostMapping("/{username}/reset-password")
+ @PreAuthorize("hasRole('ADMIN') or hasRole('SUPER_ADMIN')")
public ResponseEntity> resetUserPassword(@PathVariable String username,
@Valid @RequestBody ResetPasswordRequest resetRequest) {
try {
diff --git a/auth-service/src/main/java/com/techtorque/auth_service/dto/request/CreateAdminRequest.java b/auth-service/src/main/java/com/techtorque/auth_service/dto/request/CreateAdminRequest.java
index 1dc5e16..c4c0e78 100644
--- a/auth-service/src/main/java/com/techtorque/auth_service/dto/request/CreateAdminRequest.java
+++ b/auth-service/src/main/java/com/techtorque/auth_service/dto/request/CreateAdminRequest.java
@@ -14,11 +14,13 @@
@AllArgsConstructor
@Builder
public class CreateAdminRequest {
-
+
private String username;
private String email;
private String password;
-
+
+ private String fullName;
+
// Optional: Additional admin-specific fields
private String firstName;
private String lastName;
diff --git a/auth-service/src/main/java/com/techtorque/auth_service/dto/request/CreateEmployeeRequest.java b/auth-service/src/main/java/com/techtorque/auth_service/dto/request/CreateEmployeeRequest.java
index 20cb3f1..19e41e7 100644
--- a/auth-service/src/main/java/com/techtorque/auth_service/dto/request/CreateEmployeeRequest.java
+++ b/auth-service/src/main/java/com/techtorque/auth_service/dto/request/CreateEmployeeRequest.java
@@ -14,11 +14,13 @@
@AllArgsConstructor
@Builder
public class CreateEmployeeRequest {
-
+
private String username;
private String email;
private String password;
-
+
+ private String fullName;
+
// Optional: Additional employee-specific fields
private String firstName;
private String lastName;
diff --git a/auth-service/src/main/java/com/techtorque/auth_service/service/AuthService.java b/auth-service/src/main/java/com/techtorque/auth_service/service/AuthService.java
index 4a83235..1b26160 100644
--- a/auth-service/src/main/java/com/techtorque/auth_service/service/AuthService.java
+++ b/auth-service/src/main/java/com/techtorque/auth_service/service/AuthService.java
@@ -33,19 +33,19 @@
@Service
@Transactional
public class AuthService {
-
+
@Autowired
private AuthenticationManager authenticationManager;
-
+
@Autowired
private UserRepository userRepository;
-
+
@Autowired
private RoleRepository roleRepository;
-
+
@Autowired
private PasswordEncoder passwordEncoder;
-
+
@Autowired
private JwtUtil jwtUtil;
@@ -64,13 +64,13 @@ public class AuthService {
@Autowired
private EmailService emailService;
- @Value("${security.login.max-failed-attempts:3}")
- private int maxFailedAttempts;
+ @Value("${security.login.max-failed-attempts:3}")
+ private int maxFailedAttempts;
+
+ // duration in minutes
+ @Value("${security.login.lock-duration-minutes:15}")
+ private long lockDurationMinutes;
- // duration in minutes
- @Value("${security.login.lock-duration-minutes:15}")
- private long lockDurationMinutes;
-
public LoginResponse authenticateUser(LoginRequest loginRequest, HttpServletRequest request) {
String uname = loginRequest.getUsername();
@@ -79,32 +79,34 @@ public LoginResponse authenticateUser(LoginRequest loginRequest, HttpServletRequ
if (userOpt.isEmpty()) {
userOpt = userRepository.findByEmail(uname);
}
-
+
if (userOpt.isPresent()) {
User user = userOpt.get();
-
+
// Check if account is disabled (deactivated by admin)
if (!user.getEnabled()) {
if (!user.getEmailVerified()) {
throw new org.springframework.security.authentication.DisabledException(
- "Please verify your email address before logging in. Check your inbox for the verification link.");
+ "Please verify your email address before logging in. Check your inbox for the verification link.");
} else {
throw new org.springframework.security.authentication.DisabledException(
- "Your account has been deactivated. Please contact the administrator for assistance.");
+ "Your account has been deactivated. Please contact the administrator for assistance.");
}
}
}
- // load or create lock record
- com.techtorque.auth_service.entity.LoginLock lock = loginLockRepository.findByUsername(uname)
- .orElseGet(() -> com.techtorque.auth_service.entity.LoginLock.builder().username(uname).failedAttempts(0).build());
+ // load or create lock record
+ com.techtorque.auth_service.entity.LoginLock lock = loginLockRepository.findByUsername(uname)
+ .orElseGet(() -> com.techtorque.auth_service.entity.LoginLock.builder().username(uname)
+ .failedAttempts(0).build());
if (lock.getLockUntil() != null && lock.getLockUntil().isAfter(LocalDateTime.now())) {
long minutesLeft = ChronoUnit.MINUTES.between(LocalDateTime.now(), lock.getLockUntil());
- // record login log using audit service
- String ip = request != null ? (request.getHeader("X-Forwarded-For") == null ? request.getRemoteAddr() : request.getHeader("X-Forwarded-For")) : null;
- String ua = request != null ? request.getHeader("User-Agent") : null;
- loginAuditService.recordLogin(uname, false, ip, ua);
+ // record login log using audit service
+ String ip = request != null ? (request.getHeader("X-Forwarded-For") == null ? request.getRemoteAddr()
+ : request.getHeader("X-Forwarded-For")) : null;
+ String ua = request != null ? request.getHeader("User-Agent") : null;
+ loginAuditService.recordLogin(uname, false, ip, ua);
throw new org.springframework.security.authentication.BadCredentialsException(
"Account is temporarily locked. Try again in " + minutesLeft + " minutes.");
}
@@ -113,9 +115,7 @@ public LoginResponse authenticateUser(LoginRequest loginRequest, HttpServletRequ
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
loginRequest.getUsername(),
- loginRequest.getPassword()
- )
- );
+ loginRequest.getPassword()));
// Successful authentication -> reset failed attempts on lock record
lock.setFailedAttempts(0);
@@ -139,9 +139,10 @@ public LoginResponse authenticateUser(LoginRequest loginRequest, HttpServletRequ
.collect(Collectors.toSet());
recordLogin(uname, true, request);
-
+
// Create refresh token
- String ip = request != null ? (request.getHeader("X-Forwarded-For") == null ? request.getRemoteAddr() : request.getHeader("X-Forwarded-For")) : null;
+ String ip = request != null ? (request.getHeader("X-Forwarded-For") == null ? request.getRemoteAddr()
+ : request.getHeader("X-Forwarded-For")) : null;
String ua = request != null ? request.getHeader("User-Agent") : null;
String refreshToken = tokenService.createRefreshToken(foundUser, ip, ua);
@@ -154,14 +155,17 @@ public LoginResponse authenticateUser(LoginRequest loginRequest, HttpServletRequ
.build();
} catch (BadCredentialsException ex) {
- // increment failed attempts and possibly lock the user using separate transaction
+ // increment failed attempts and possibly lock the user using separate
+ // transaction
loginAuditService.incrementFailedAttempt(uname, lockDurationMinutes, maxFailedAttempts);
- String ip = request != null ? (request.getHeader("X-Forwarded-For") == null ? request.getRemoteAddr() : request.getHeader("X-Forwarded-For")) : null;
+ String ip = request != null ? (request.getHeader("X-Forwarded-For") == null ? request.getRemoteAddr()
+ : request.getHeader("X-Forwarded-For")) : null;
String ua = request != null ? request.getHeader("User-Agent") : null;
loginAuditService.recordLogin(uname, false, ip, ua);
- throw new org.springframework.security.authentication.BadCredentialsException("Invalid username or password");
+ throw new org.springframework.security.authentication.BadCredentialsException(
+ "Invalid username or password");
}
}
@@ -170,7 +174,8 @@ private void recordLogin(String username, boolean success, HttpServletRequest re
String ua = null;
if (request != null) {
ip = request.getHeader("X-Forwarded-For");
- if (ip == null) ip = request.getRemoteAddr();
+ if (ip == null)
+ ip = request.getRemoteAddr();
ua = request.getHeader("User-Agent");
}
com.techtorque.auth_service.entity.LoginLog log = com.techtorque.auth_service.entity.LoginLog.builder()
@@ -182,12 +187,12 @@ private void recordLogin(String username, boolean success, HttpServletRequest re
.build();
loginLogRepository.save(log);
}
-
+
public String registerUser(RegisterRequest registerRequest) {
if (userRepository.existsByEmail(registerRequest.getEmail())) {
throw new RuntimeException("Error: Email is already in use!");
}
-
+
User user = User.builder()
.username(registerRequest.getEmail()) // Use email as username for simplicity
.email(registerRequest.getEmail())
@@ -200,10 +205,10 @@ public String registerUser(RegisterRequest registerRequest) {
.emailVerificationDeadline(LocalDateTime.now().plus(7, ChronoUnit.DAYS)) // 1 week deadline
.roles(new HashSet<>())
.build();
-
+
Set strRoles = registerRequest.getRoles();
Set roles = new HashSet<>();
-
+
if (strRoles == null || strRoles.isEmpty()) {
Role customerRole = roleRepository.findByName(RoleName.CUSTOMER)
.orElseThrow(() -> new RuntimeException("Error: Customer Role not found."));
@@ -228,57 +233,58 @@ public String registerUser(RegisterRequest registerRequest) {
}
});
}
-
+
user.setRoles(roles);
User savedUser = userRepository.save(user);
-
+
// Create verification token and send email
String token = tokenService.createVerificationToken(savedUser);
- emailService.sendVerificationEmail(savedUser.getEmail(), savedUser.getUsername(), token);
-
+ emailService.sendVerificationEmail(savedUser.getEmail(), savedUser.getFullName(), token);
+
return "User registered successfully! Please check your email to verify your account.";
}
-
+
/**
* Verify email with token
*/
public LoginResponse verifyEmail(String token, HttpServletRequest request) {
- com.techtorque.auth_service.entity.VerificationToken verificationToken =
- tokenService.validateToken(token, com.techtorque.auth_service.entity.VerificationToken.TokenType.EMAIL_VERIFICATION);
-
+ com.techtorque.auth_service.entity.VerificationToken verificationToken = tokenService.validateToken(token,
+ com.techtorque.auth_service.entity.VerificationToken.TokenType.EMAIL_VERIFICATION);
+
User user = verificationToken.getUser();
user.setEnabled(true);
user.setEmailVerified(true); // Mark email as verified
User updatedUser = userRepository.save(user);
tokenService.markTokenAsUsed(verificationToken);
-
+
// Send welcome email
emailService.sendWelcomeEmail(updatedUser.getEmail(), updatedUser.getUsername());
// Auto-login after verification
- Set roleNames = updatedUser.getRoles() != null ?
- updatedUser.getRoles().stream()
+ Set roleNames = updatedUser.getRoles() != null ? updatedUser.getRoles().stream()
.map(role -> role.getName().name())
- .collect(Collectors.toSet()) :
- Set.of("CUSTOMER");
+ .collect(Collectors.toSet()) : Set.of("CUSTOMER");
List roles = new java.util.ArrayList<>(roleNames);
Set authorities = new java.util.HashSet<>();
if (updatedUser.getRoles() != null) {
updatedUser.getRoles().stream()
- .flatMap(role -> role.getPermissions() != null ? role.getPermissions().stream() : java.util.stream.Stream.empty())
- .forEach(permission -> authorities.add(new org.springframework.security.core.authority.SimpleGrantedAuthority(permission.getName())));
+ .flatMap(role -> role.getPermissions() != null ? role.getPermissions().stream()
+ : java.util.stream.Stream.empty())
+ .forEach(permission -> authorities
+ .add(new org.springframework.security.core.authority.SimpleGrantedAuthority(
+ permission.getName())));
}
String jwt = jwtUtil.generateJwtToken(new org.springframework.security.core.userdetails.User(
updatedUser.getUsername(),
updatedUser.getPassword(),
- authorities
- ), roles);
-
- String ip = request != null ? (request.getHeader("X-Forwarded-For") == null ? request.getRemoteAddr() : request.getHeader("X-Forwarded-For")) : null;
+ authorities), roles);
+
+ String ip = request != null ? (request.getHeader("X-Forwarded-For") == null ? request.getRemoteAddr()
+ : request.getHeader("X-Forwarded-For")) : null;
String ua = request != null ? request.getHeader("User-Agent") : null;
String refreshToken = tokenService.createRefreshToken(updatedUser, ip, ua);
@@ -290,30 +296,31 @@ public LoginResponse verifyEmail(String token, HttpServletRequest request) {
.roles(roleNames)
.build();
}
-
+
/**
* Resend verification email
*/
public String resendVerificationEmail(String email) {
User user = userRepository.findByEmail(email)
.orElseThrow(() -> new RuntimeException("User not found with email: " + email));
-
+
if (user.getEmailVerified()) {
throw new RuntimeException("Email is already verified");
}
-
+
String token = tokenService.createVerificationToken(user);
emailService.sendVerificationEmail(user.getEmail(), user.getUsername(), token);
-
+
return "Verification email sent successfully!";
}
-
+
/**
* Refresh JWT token
*/
public LoginResponse refreshToken(String refreshTokenString) {
- com.techtorque.auth_service.entity.RefreshToken refreshToken = tokenService.validateRefreshToken(refreshTokenString);
-
+ com.techtorque.auth_service.entity.RefreshToken refreshToken = tokenService
+ .validateRefreshToken(refreshTokenString);
+
User user = refreshToken.getUser();
List roles = user.getRoles().stream()
@@ -325,14 +332,15 @@ public LoginResponse refreshToken(String refreshTokenString) {
user.getPassword(),
user.getRoles().stream()
.flatMap(role -> role.getPermissions().stream())
- .map(permission -> new org.springframework.security.core.authority.SimpleGrantedAuthority(permission.getName()))
- .collect(Collectors.toSet())
- ), roles);
-
+ .map(permission -> new org.springframework.security.core.authority.SimpleGrantedAuthority(
+ permission.getName()))
+ .collect(Collectors.toSet())),
+ roles);
+
Set roleNames = user.getRoles().stream()
.map(role -> role.getName().name())
.collect(Collectors.toSet());
-
+
return LoginResponse.builder()
.token(jwt)
.refreshToken(refreshTokenString) // Return same refresh token
@@ -341,43 +349,43 @@ public LoginResponse refreshToken(String refreshTokenString) {
.roles(roleNames)
.build();
}
-
+
/**
* Logout - revoke refresh token
*/
public void logout(String refreshToken) {
tokenService.revokeRefreshToken(refreshToken);
}
-
+
/**
* Request password reset
*/
public String forgotPassword(String email) {
User user = userRepository.findByEmail(email)
.orElseThrow(() -> new RuntimeException("User not found with email: " + email));
-
+
String token = tokenService.createPasswordResetToken(user);
emailService.sendPasswordResetEmail(user.getEmail(), user.getUsername(), token);
-
+
return "Password reset email sent successfully!";
}
-
+
/**
* Reset password with token
*/
public String resetPassword(String token, String newPassword) {
- com.techtorque.auth_service.entity.VerificationToken resetToken =
- tokenService.validateToken(token, com.techtorque.auth_service.entity.VerificationToken.TokenType.PASSWORD_RESET);
-
+ com.techtorque.auth_service.entity.VerificationToken resetToken = tokenService.validateToken(token,
+ com.techtorque.auth_service.entity.VerificationToken.TokenType.PASSWORD_RESET);
+
User user = resetToken.getUser();
user.setPassword(passwordEncoder.encode(newPassword));
userRepository.save(user);
-
+
tokenService.markTokenAsUsed(resetToken);
-
+
// Revoke all existing refresh tokens for security
tokenService.revokeAllUserTokens(user);
-
+
return "Password reset successfully!";
}
}
diff --git a/auth-service/src/main/java/com/techtorque/auth_service/service/UserService.java b/auth-service/src/main/java/com/techtorque/auth_service/service/UserService.java
index 39f8135..3b50bc3 100644
--- a/auth-service/src/main/java/com/techtorque/auth_service/service/UserService.java
+++ b/auth-service/src/main/java/com/techtorque/auth_service/service/UserService.java
@@ -37,17 +37,21 @@
@Transactional
public class UserService implements UserDetailsService {
- private final UserRepository userRepository;
- private final RoleRepository roleRepository;
- private final PasswordEncoder passwordEncoder;
- private final LoginLockRepository loginLockRepository;
- private final RefreshTokenRepository refreshTokenRepository;
- private final VerificationTokenRepository verificationTokenRepository;
- private final LoginLogRepository loginLogRepository;
-
- public UserService(UserRepository userRepository, RoleRepository roleRepository, @Lazy PasswordEncoder passwordEncoder,
- LoginLockRepository loginLockRepository, RefreshTokenRepository refreshTokenRepository,
- VerificationTokenRepository verificationTokenRepository, LoginLogRepository loginLogRepository) {
+ private final UserRepository userRepository;
+ private final RoleRepository roleRepository;
+ private final PasswordEncoder passwordEncoder;
+ private final LoginLockRepository loginLockRepository;
+ private final RefreshTokenRepository refreshTokenRepository;
+ private final VerificationTokenRepository verificationTokenRepository;
+ private final LoginLogRepository loginLogRepository;
+ private final EmailService emailService;
+ private final TokenService tokenService;
+
+ public UserService(UserRepository userRepository, RoleRepository roleRepository,
+ @Lazy PasswordEncoder passwordEncoder,
+ LoginLockRepository loginLockRepository, RefreshTokenRepository refreshTokenRepository,
+ VerificationTokenRepository verificationTokenRepository, LoginLogRepository loginLogRepository,
+ EmailService emailService, TokenService tokenService) {
this.userRepository = userRepository;
this.roleRepository = roleRepository;
this.passwordEncoder = passwordEncoder;
@@ -55,11 +59,14 @@ public UserService(UserRepository userRepository, RoleRepository roleRepository,
this.refreshTokenRepository = refreshTokenRepository;
this.verificationTokenRepository = verificationTokenRepository;
this.loginLogRepository = loginLogRepository;
+ this.emailService = emailService;
+ this.tokenService = tokenService;
}
/**
* Load user by username for Spring Security authentication
* This method is called during login to authenticate the user
+ *
* @param username The username to authenticate
* @return UserDetails object with user info and authorities
* @throws UsernameNotFoundException if user not found
@@ -87,32 +94,34 @@ public UserDetails loadUserByUsername(String identifier) throws UsernameNotFound
}
/**
- * Convert user roles and permissions to Spring Security GrantedAuthority objects
+ * Convert user roles and permissions to Spring Security GrantedAuthority
+ * objects
* This enables role-based and permission-based security checks
+ *
* @param user The user whose authorities to build
* @return Collection of granted authorities
*/
private Collection extends GrantedAuthority> getAuthorities(User user) {
Set authorities = new HashSet<>();
-
+
// Add role-based authorities (prefixed with ROLE_)
user.getRoles().forEach(role -> {
authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName().name()));
-
+
// Add permission-based authorities (used for @PreAuthorize checks)
- role.getPermissions().forEach(permission ->
- authorities.add(new SimpleGrantedAuthority(permission.getName()))
- );
+ role.getPermissions()
+ .forEach(permission -> authorities.add(new SimpleGrantedAuthority(permission.getName())));
});
-
+
return authorities;
}
/**
* Register a new customer (public registration)
* Only allows CUSTOMER role creation through public endpoint
+ *
* @param username Unique username
- * @param email Unique email
+ * @param email Unique email
* @param password Plain text password (will be encoded)
* @return The created customer user
* @throws RuntimeException if username or email already exists
@@ -122,15 +131,15 @@ public User registerCustomer(String username, String email, String password) {
if (userRepository.findByUsername(username).isPresent()) {
throw new IllegalArgumentException("Username already exists: " + username);
}
-
+
// Validate email doesn't exist
if (userRepository.findByEmail(email).isPresent()) {
throw new IllegalArgumentException("Email already exists: " + email);
}
// Get CUSTOMER role from database
- Role customerRole = roleRepository.findByName(RoleName.CUSTOMER)
- .orElseThrow(() -> new EntityNotFoundException("Customer role not found"));
+ Role customerRole = roleRepository.findByName(RoleName.CUSTOMER)
+ .orElseThrow(() -> new EntityNotFoundException("Customer role not found"));
// Create user with CUSTOMER role only
User user = User.builder()
@@ -147,77 +156,96 @@ public User registerCustomer(String username, String email, String password) {
/**
* Create an employee account (admin only)
* Only admins can call this method
+ *
* @param username Unique username
- * @param email Unique email
+ * @param email Unique email
* @param password Plain text password (will be encoded)
+ * @param fullName Full name of the employee
* @return The created employee user
* @throws RuntimeException if username or email already exists
*/
- public User createEmployee(String username, String email, String password) {
+ public User createEmployee(String username, String email, String password, String fullName) {
// Validate username doesn't exist
if (userRepository.findByUsername(username).isPresent()) {
throw new IllegalArgumentException("Username already exists: " + username);
}
-
+
// Validate email doesn't exist
if (userRepository.findByEmail(email).isPresent()) {
throw new IllegalArgumentException("Email already exists: " + email);
}
// Get EMPLOYEE role from database
- Role employeeRole = roleRepository.findByName(RoleName.EMPLOYEE)
- .orElseThrow(() -> new EntityNotFoundException("Employee role not found"));
+ Role employeeRole = roleRepository.findByName(RoleName.EMPLOYEE)
+ .orElseThrow(() -> new EntityNotFoundException("Employee role not found"));
// Create user with EMPLOYEE role
User user = User.builder()
.username(username)
.email(email)
.password(passwordEncoder.encode(password))
+ .fullName(fullName)
.enabled(true)
.roles(Set.of(employeeRole)) // Only EMPLOYEE role
.build();
- return userRepository.save(user);
+ User savedUser = userRepository.save(user);
+
+ // Create verification token and send email
+ String token = tokenService.createVerificationToken(savedUser);
+ emailService.sendVerificationEmail(savedUser.getEmail(), savedUser.getFullName(), token);
+
+ return savedUser;
}
/**
* Create an admin account (admin only)
* Only existing admins can call this method
+ *
* @param username Unique username
- * @param email Unique email
+ * @param email Unique email
* @param password Plain text password (will be encoded)
+ * @param fullName Full name of the admin
* @return The created admin user
* @throws RuntimeException if username or email already exists
*/
- public User createAdmin(String username, String email, String password) {
+ public User createAdmin(String username, String email, String password, String fullName) {
// Validate username doesn't exist
if (userRepository.findByUsername(username).isPresent()) {
throw new IllegalArgumentException("Username already exists: " + username);
}
-
+
// Validate email doesn't exist
if (userRepository.findByEmail(email).isPresent()) {
throw new IllegalArgumentException("Email already exists: " + email);
}
// Get ADMIN role from database
- Role adminRole = roleRepository.findByName(RoleName.ADMIN)
- .orElseThrow(() -> new EntityNotFoundException("Admin role not found"));
+ Role adminRole = roleRepository.findByName(RoleName.ADMIN)
+ .orElseThrow(() -> new EntityNotFoundException("Admin role not found"));
// Create user with ADMIN role
User user = User.builder()
.username(username)
.email(email)
.password(passwordEncoder.encode(password))
+ .fullName(fullName)
.enabled(true)
.roles(Set.of(adminRole)) // Only ADMIN role
.build();
- return userRepository.save(user);
+ User savedUser = userRepository.save(user);
+
+ // Create verification token and send email
+ String token = tokenService.createVerificationToken(savedUser);
+ emailService.sendVerificationEmail(savedUser.getEmail(), savedUser.getFullName(), token);
+
+ return savedUser;
}
/**
* Find user by username
+ *
* @param username Username to search for
* @return Optional containing user if found
*/
@@ -227,6 +255,7 @@ public Optional findByUsername(String username) {
/**
* Find user by email
+ *
* @param email Email to search for
* @return Optional containing user if found
*/
@@ -236,6 +265,7 @@ public Optional findByEmail(String email) {
/**
* Get all users in the system (admin only)
+ *
* @return List of all users
*/
public List findAllUsers() {
@@ -244,13 +274,14 @@ public List findAllUsers() {
/**
* Get all permissions for a user (from all their roles)
+ *
* @param username Username to get permissions for
* @return Set of permission names
*/
public Set getUserPermissions(String username) {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new EntityNotFoundException("User not found: " + username));
-
+
return user.getRoles().stream()
.flatMap(role -> role.getPermissions().stream())
.map(permission -> permission.getName())
@@ -259,13 +290,14 @@ public Set getUserPermissions(String username) {
/**
* Get all roles for a user
+ *
* @param username Username to get roles for
* @return Set of role names
*/
public Set getUserRoles(String username) {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new EntityNotFoundException("User not found: " + username));
-
+
return user.getRoles().stream()
.map(role -> role.getName().name())
.collect(Collectors.toSet());
@@ -273,6 +305,7 @@ public Set getUserRoles(String username) {
/**
* Enable a user account (admin only)
+ *
* @param username Username to enable
*/
public void enableUser(String username) {
@@ -284,6 +317,7 @@ public void enableUser(String username) {
/**
* Disable a user account (admin only)
+ *
* @param username Username to disable
*/
public void disableUser(String username) {
@@ -295,6 +329,7 @@ public void disableUser(String username) {
/**
* Delete a user from the system (admin only)
+ *
* @param username Username to delete
* @throws RuntimeException if user not found
*/
@@ -302,7 +337,8 @@ public void deleteUser(String username) {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new EntityNotFoundException("User not found: " + username));
- // Clear all related records before deleting the user to avoid foreign key constraint issues
+ // Clear all related records before deleting the user to avoid foreign key
+ // constraint issues
// Clear roles
user.getRoles().clear();
@@ -329,7 +365,8 @@ public void deleteUser(String username) {
* Resets failed attempts and lock timestamp if an entry exists.
*/
public void clearLoginLock(String username) {
- java.util.Optional lockOpt = loginLockRepository.findByUsername(username);
+ java.util.Optional lockOpt = loginLockRepository
+ .findByUsername(username);
if (lockOpt.isPresent()) {
com.techtorque.auth_service.entity.LoginLock lock = lockOpt.get();
lock.setFailedAttempts(0);
@@ -340,6 +377,7 @@ public void clearLoginLock(String username) {
/**
* Check if a user has a specific role
+ *
* @param username Username to check
* @param roleName Role to check for
* @return true if user has the role
@@ -347,21 +385,22 @@ public void clearLoginLock(String username) {
public boolean hasRole(String username, RoleName roleName) {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new EntityNotFoundException("User not found: " + username));
-
+
return user.getRoles().stream()
.anyMatch(role -> role.getName().equals(roleName));
}
/**
* Check if a user has a specific permission
- * @param username Username to check
+ *
+ * @param username Username to check
* @param permissionName Permission to check for
* @return true if user has the permission through any of their roles
*/
public boolean hasPermission(String username, String permissionName) {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new EntityNotFoundException("User not found: " + username));
-
+
return user.getRoles().stream()
.flatMap(role -> role.getPermissions().stream())
.anyMatch(permission -> permission.getName().equals(permissionName));
@@ -369,17 +408,18 @@ public boolean hasPermission(String username, String permissionName) {
/**
* Update user details (admin only)
- * @param username Username of the user to update
+ *
+ * @param username Username of the user to update
* @param newUsername New username (optional)
- * @param newEmail New email (optional)
- * @param enabled New enabled status (optional)
+ * @param newEmail New email (optional)
+ * @param enabled New enabled status (optional)
* @return Updated user
* @throws RuntimeException if user not found or new values already exist
*/
public User updateUserDetails(String username, String newUsername, String newEmail, Boolean enabled) {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new EntityNotFoundException("User not found: " + username));
-
+
// Check if new username is provided and different
if (newUsername != null && !newUsername.equals(user.getUsername())) {
if (userRepository.existsByUsername(newUsername)) {
@@ -387,7 +427,7 @@ public User updateUserDetails(String username, String newUsername, String newEma
}
user.setUsername(newUsername);
}
-
+
// Check if new email is provided and different
if (newEmail != null && !newEmail.equals(user.getEmail())) {
if (userRepository.existsByEmail(newEmail)) {
@@ -395,51 +435,54 @@ public User updateUserDetails(String username, String newUsername, String newEma
}
user.setEmail(newEmail);
}
-
+
// Update enabled status if provided
if (enabled != null) {
user.setEnabled(enabled);
}
-
+
return userRepository.save(user);
}
/**
* Reset a user's password (admin only)
- * @param username Username whose password to reset
+ *
+ * @param username Username whose password to reset
* @param newPassword New password (plain text)
* @throws RuntimeException if user not found
*/
public void resetUserPassword(String username, String newPassword) {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new EntityNotFoundException("User not found: " + username));
-
+
user.setPassword(passwordEncoder.encode(newPassword));
userRepository.save(user);
}
/**
* Change user's own password (requires current password verification)
- * @param username Username of the user changing password
+ *
+ * @param username Username of the user changing password
* @param currentPassword Current password for verification
- * @param newPassword New password (plain text)
+ * @param newPassword New password (plain text)
* @throws RuntimeException if user not found or current password is incorrect
*/
public void changeUserPassword(String username, String currentPassword, String newPassword) {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new EntityNotFoundException("User not found: " + username));
-
+
// Verify current password
if (!passwordEncoder.matches(currentPassword, user.getPassword())) {
throw new IllegalStateException("Current password is incorrect");
}
-
+
user.setPassword(passwordEncoder.encode(newPassword));
userRepository.save(user);
}
/**
* Assign a role to a user (admin only)
+ *
* @param username Username to assign role to
* @param roleName Role name to assign
* @throws RuntimeException if user or role not found, or role already assigned
@@ -447,14 +490,14 @@ public void changeUserPassword(String username, String currentPassword, String n
public void assignRoleToUser(String username, String roleName) {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new EntityNotFoundException("User not found: " + username));
-
+
RoleName roleNameEnum;
try {
roleNameEnum = RoleName.valueOf(roleName.toUpperCase());
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Invalid role name: " + roleName);
}
-
+
// Rule: Only a SUPER_ADMIN can assign the ADMIN role.
if (roleNameEnum == RoleName.ADMIN) {
Authentication currentUser = SecurityContextHolder.getContext().getAuthentication();
@@ -468,20 +511,21 @@ public void assignRoleToUser(String username, String roleName) {
throw new AccessDeniedException("Permission denied: Only a SUPER_ADMIN can assign the ADMIN role.");
}
}
-
- Role role = roleRepository.findByName(roleNameEnum)
- .orElseThrow(() -> new EntityNotFoundException("Role not found: " + roleName));
-
+
+ Role role = roleRepository.findByName(roleNameEnum)
+ .orElseThrow(() -> new EntityNotFoundException("Role not found: " + roleName));
+
if (user.getRoles().contains(role)) {
throw new IllegalStateException("User already has role: " + roleName);
}
-
+
user.getRoles().add(role);
userRepository.save(user);
}
/**
* Revoke a role from a user (admin only)
+ *
* @param username Username to revoke role from
* @param roleName Role name to revoke
* @throws RuntimeException if user or role not found, or role not assigned
@@ -489,14 +533,14 @@ public void assignRoleToUser(String username, String roleName) {
public void revokeRoleFromUser(String username, String roleName) {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new EntityNotFoundException("User not found: " + username));
-
+
RoleName roleNameEnum;
try {
roleNameEnum = RoleName.valueOf(roleName.toUpperCase());
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Invalid role name: " + roleName);
}
-
+
// Rule: A user cannot revoke their own SUPER_ADMIN role.
if (roleNameEnum == RoleName.SUPER_ADMIN) {
Authentication currentUser = SecurityContextHolder.getContext().getAuthentication();
@@ -506,28 +550,29 @@ public void revokeRoleFromUser(String username, String roleName) {
String currentUsername = currentUser.getName();
if (currentUsername.equals(username)) {
- throw new AccessDeniedException("Action denied: A SUPER_ADMIN cannot revoke their own SUPER_ADMIN role.");
+ throw new AccessDeniedException(
+ "Action denied: A SUPER_ADMIN cannot revoke their own SUPER_ADMIN role.");
}
}
- Role role = roleRepository.findByName(roleNameEnum)
- .orElseThrow(() -> new EntityNotFoundException("Role not found: " + roleName));
-
- if (!user.getRoles().contains(role)) {
- throw new IllegalStateException("User does not have role: " + roleName);
- }
-
+ Role role = roleRepository.findByName(roleNameEnum)
+ .orElseThrow(() -> new EntityNotFoundException("Role not found: " + roleName));
+
+ if (!user.getRoles().contains(role)) {
+ throw new IllegalStateException("User does not have role: " + roleName);
+ }
+
user.getRoles().remove(role);
userRepository.save(user);
}
-
+
/**
* Update user profile
*/
public User updateProfile(String username, String fullName, String phone, String address) {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new EntityNotFoundException("User not found: " + username));
-
+
if (fullName != null) {
user.setFullName(fullName);
}
@@ -537,17 +582,17 @@ public User updateProfile(String username, String fullName, String phone, String
if (address != null) {
user.setAddress(address);
}
-
+
return userRepository.save(user);
}
-
+
/**
* Update profile photo URL
*/
public User updateProfilePhoto(String username, String photoUrl) {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new EntityNotFoundException("User not found: " + username));
-
+
user.setProfilePhotoUrl(photoUrl);
return userRepository.save(user);
}
diff --git a/auth-service/src/test/java/com/techtorque/auth_service/controller/UserControllerTest.java b/auth-service/src/test/java/com/techtorque/auth_service/controller/UserControllerTest.java
new file mode 100644
index 0000000..dd67821
--- /dev/null
+++ b/auth-service/src/test/java/com/techtorque/auth_service/controller/UserControllerTest.java
@@ -0,0 +1,46 @@
+package com.techtorque.auth_service.controller;
+
+import com.techtorque.auth_service.entity.User;
+import com.techtorque.auth_service.service.UserService;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.web.servlet.MockMvc;
+
+import java.util.Optional;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@WebMvcTest(UserController.class)
+class UserControllerTest {
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @MockBean
+ private UserService userService;
+
+ @MockBean
+ private com.techtorque.auth_service.service.PreferencesService preferencesService;
+
+ @Test
+ @WithMockUser(username = "testuser", roles = {"CUSTOMER"})
+ void getCurrentUserProfile_whenAuthenticatedCustomer_shouldReturnProfile() throws Exception {
+ User user = new User();
+ user.setUsername("testuser");
+ user.setFullName("Test User");
+
+ when(userService.findByUsername(anyString())).thenReturn(Optional.of(user));
+
+ mockMvc.perform(get("/users/me"))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.username").value("testuser"))
+ .andExpect(jsonPath("$.fullName").value("Test User"));
+ }
+}
diff --git a/auth-service/src/test/java/com/techtorque/auth_service/repository/LoginLockRepositoryTest.java b/auth-service/src/test/java/com/techtorque/auth_service/repository/LoginLockRepositoryTest.java
new file mode 100644
index 0000000..f9a919d
--- /dev/null
+++ b/auth-service/src/test/java/com/techtorque/auth_service/repository/LoginLockRepositoryTest.java
@@ -0,0 +1,286 @@
+package com.techtorque.auth_service.repository;
+
+import com.techtorque.auth_service.entity.LoginLock;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Optional;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Comprehensive test class for LoginLockRepository
+ * Tests all repository methods, edge cases, and database constraints
+ */
+@DataJpaTest
+@ActiveProfiles("test")
+class LoginLockRepositoryTest {
+
+ @Autowired
+ private TestEntityManager entityManager;
+
+ @Autowired
+ private LoginLockRepository loginLockRepository;
+
+ private LoginLock testLoginLock;
+
+ @BeforeEach
+ void setUp() {
+ testLoginLock = LoginLock.builder()
+ .username("testuser")
+ .failedAttempts(3)
+ .lockUntil(LocalDateTime.now().plusMinutes(15))
+ .build();
+ }
+
+ @Test
+ void save_WhenValidLoginLock_ShouldPersistSuccessfully() {
+ // When
+ LoginLock savedLock = loginLockRepository.save(testLoginLock);
+
+ // Then
+ assertThat(savedLock.getId()).isNotNull();
+ assertThat(savedLock.getUsername()).isEqualTo("testuser");
+ assertThat(savedLock.getFailedAttempts()).isEqualTo(3);
+ assertThat(savedLock.getLockUntil()).isNotNull();
+ }
+
+ @Test
+ void save_WithDefaultFailedAttempts_ShouldUseZero() {
+ // Given
+ LoginLock lockWithDefaults = LoginLock.builder()
+ .username("newuser")
+ .build();
+
+ // When
+ LoginLock savedLock = loginLockRepository.save(lockWithDefaults);
+
+ // Then
+ assertThat(savedLock.getId()).isNotNull();
+ assertThat(savedLock.getUsername()).isEqualTo("newuser");
+ assertThat(savedLock.getFailedAttempts()).isEqualTo(0);
+ assertThat(savedLock.getLockUntil()).isNull();
+ }
+
+ @Test
+ void findByUsername_WhenLockExists_ShouldReturnLock() {
+ // Given
+ entityManager.persistAndFlush(testLoginLock);
+
+ // When
+ Optional result = loginLockRepository.findByUsername("testuser");
+
+ // Then
+ assertThat(result).isPresent();
+ assertThat(result.get().getUsername()).isEqualTo("testuser");
+ assertThat(result.get().getFailedAttempts()).isEqualTo(3);
+ }
+
+ @Test
+ void findByUsername_WhenLockDoesNotExist_ShouldReturnEmpty() {
+ // When
+ Optional result = loginLockRepository.findByUsername("nonexistent");
+
+ // Then
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ void findByUsername_CaseSensitive_ShouldReturnEmpty() {
+ // Given
+ entityManager.persistAndFlush(testLoginLock);
+
+ // When
+ Optional result = loginLockRepository.findByUsername("TESTUSER");
+
+ // Then
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ @Transactional
+ void deleteByUsername_WhenLockExists_ShouldDeleteLock() {
+ // Given
+ entityManager.persistAndFlush(testLoginLock);
+
+ LoginLock anotherLock = LoginLock.builder()
+ .username("anotheruser")
+ .failedAttempts(1)
+ .build();
+ entityManager.persistAndFlush(anotherLock);
+
+ // When
+ loginLockRepository.deleteByUsername("testuser");
+ entityManager.flush();
+
+ // Then
+ Optional deletedLock = loginLockRepository.findByUsername("testuser");
+ Optional remainingLock = loginLockRepository.findByUsername("anotheruser");
+
+ assertThat(deletedLock).isEmpty();
+ assertThat(remainingLock).isPresent();
+ }
+
+ @Test
+ void update_WhenLockExists_ShouldUpdateSuccessfully() {
+ // Given
+ LoginLock savedLock = entityManager.persistAndFlush(testLoginLock);
+ entityManager.detach(savedLock);
+
+ // When
+ savedLock.setFailedAttempts(5);
+ savedLock.setLockUntil(LocalDateTime.now().plusMinutes(30));
+ LoginLock updatedLock = loginLockRepository.save(savedLock);
+
+ // Then
+ assertThat(updatedLock.getFailedAttempts()).isEqualTo(5);
+ assertThat(updatedLock.getLockUntil()).isNotNull();
+ assertThat(updatedLock.getUsername()).isEqualTo("testuser"); // Should remain unchanged
+ }
+
+ @Test
+ void findById_WhenLockExists_ShouldReturnLock() {
+ // Given
+ LoginLock savedLock = entityManager.persistAndFlush(testLoginLock);
+
+ // When
+ Optional result = loginLockRepository.findById(savedLock.getId());
+
+ // Then
+ assertThat(result).isPresent();
+ assertThat(result.get().getId()).isEqualTo(savedLock.getId());
+ assertThat(result.get().getUsername()).isEqualTo("testuser");
+ }
+
+ @Test
+ void findById_WhenLockDoesNotExist_ShouldReturnEmpty() {
+ // When
+ Optional result = loginLockRepository.findById(999L);
+
+ // Then
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ void findAll_ShouldReturnAllLocks() {
+ // Given
+ LoginLock lock1 = LoginLock.builder()
+ .username("user1")
+ .failedAttempts(2)
+ .lockUntil(LocalDateTime.now().plusMinutes(10))
+ .build();
+
+ LoginLock lock2 = LoginLock.builder()
+ .username("user2")
+ .failedAttempts(1)
+ .build();
+
+ entityManager.persistAndFlush(lock1);
+ entityManager.persistAndFlush(lock2);
+
+ // When
+ List allLocks = loginLockRepository.findAll();
+
+ // Then
+ assertThat(allLocks).hasSize(2);
+ assertThat(allLocks).extracting("username")
+ .containsExactlyInAnyOrder("user1", "user2");
+ }
+
+ @Test
+ void count_ShouldReturnCorrectCount() {
+ // Given
+ entityManager.persistAndFlush(testLoginLock);
+
+ LoginLock secondLock = LoginLock.builder()
+ .username("user2")
+ .failedAttempts(1)
+ .build();
+ entityManager.persistAndFlush(secondLock);
+
+ // When
+ long count = loginLockRepository.count();
+
+ // Then
+ assertThat(count).isEqualTo(2);
+ }
+
+ @Test
+ void save_WithoutLockUntil_ShouldPersistSuccessfully() {
+ // Given
+ LoginLock lockWithoutLockUntil = LoginLock.builder()
+ .username("testuser2")
+ .failedAttempts(1)
+ .lockUntil(null)
+ .build();
+
+ // When
+ LoginLock savedLock = loginLockRepository.save(lockWithoutLockUntil);
+
+ // Then
+ assertThat(savedLock.getId()).isNotNull();
+ assertThat(savedLock.getUsername()).isEqualTo("testuser2");
+ assertThat(savedLock.getFailedAttempts()).isEqualTo(1);
+ assertThat(savedLock.getLockUntil()).isNull();
+ }
+
+ @Test
+ void save_WithZeroFailedAttempts_ShouldPersistSuccessfully() {
+ // Given
+ LoginLock lockWithZeroAttempts = LoginLock.builder()
+ .username("testuser3")
+ .failedAttempts(0)
+ .build();
+
+ // When
+ LoginLock savedLock = loginLockRepository.save(lockWithZeroAttempts);
+
+ // Then
+ assertThat(savedLock.getId()).isNotNull();
+ assertThat(savedLock.getUsername()).isEqualTo("testuser3");
+ assertThat(savedLock.getFailedAttempts()).isEqualTo(0);
+ }
+
+ @Test
+ void existsById_WhenLockExists_ShouldReturnTrue() {
+ // Given
+ LoginLock savedLock = entityManager.persistAndFlush(testLoginLock);
+
+ // When
+ boolean exists = loginLockRepository.existsById(savedLock.getId());
+
+ // Then
+ assertThat(exists).isTrue();
+ }
+
+ @Test
+ void existsById_WhenLockDoesNotExist_ShouldReturnFalse() {
+ // When
+ boolean exists = loginLockRepository.existsById(999L);
+
+ // Then
+ assertThat(exists).isFalse();
+ }
+
+ @Test
+ void deleteById_WhenLockExists_ShouldRemoveLock() {
+ // Given
+ LoginLock savedLock = entityManager.persistAndFlush(testLoginLock);
+ Long lockId = savedLock.getId();
+
+ // When
+ loginLockRepository.deleteById(lockId);
+ entityManager.flush();
+
+ // Then
+ Optional result = loginLockRepository.findById(lockId);
+ assertThat(result).isEmpty();
+ }
+}
\ No newline at end of file
diff --git a/auth-service/src/test/java/com/techtorque/auth_service/repository/LoginLogRepositoryTest.java b/auth-service/src/test/java/com/techtorque/auth_service/repository/LoginLogRepositoryTest.java
new file mode 100644
index 0000000..0dbe12f
--- /dev/null
+++ b/auth-service/src/test/java/com/techtorque/auth_service/repository/LoginLogRepositoryTest.java
@@ -0,0 +1,240 @@
+package com.techtorque.auth_service.repository;
+
+import com.techtorque.auth_service.entity.LoginLog;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Optional;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Comprehensive test class for LoginLogRepository
+ * Tests all repository methods, edge cases, and database constraints
+ */
+@DataJpaTest
+@ActiveProfiles("test")
+class LoginLogRepositoryTest {
+
+ @Autowired
+ private TestEntityManager entityManager;
+
+ @Autowired
+ private LoginLogRepository loginLogRepository;
+
+ private LoginLog testLoginLog;
+
+ @BeforeEach
+ void setUp() {
+ testLoginLog = LoginLog.builder()
+ .username("testuser")
+ .success(true)
+ .ipAddress("192.168.1.1")
+ .userAgent("Mozilla/5.0 Test Browser")
+ .createdAt(LocalDateTime.now())
+ .build();
+ }
+
+ @Test
+ void save_WhenValidLoginLog_ShouldPersistSuccessfully() {
+ // When
+ LoginLog savedLog = loginLogRepository.save(testLoginLog);
+
+ // Then
+ assertThat(savedLog.getId()).isNotNull();
+ assertThat(savedLog.getUsername()).isEqualTo("testuser");
+ assertThat(savedLog.getSuccess()).isTrue();
+ assertThat(savedLog.getIpAddress()).isEqualTo("192.168.1.1");
+ assertThat(savedLog.getUserAgent()).isEqualTo("Mozilla/5.0 Test Browser");
+ assertThat(savedLog.getCreatedAt()).isNotNull();
+ }
+
+ @Test
+ void save_WithFailedLogin_ShouldPersistSuccessfully() {
+ // Given
+ LoginLog failedLog = LoginLog.builder()
+ .username("testuser")
+ .success(false)
+ .ipAddress("192.168.1.2")
+ .userAgent("Chrome Test")
+ .createdAt(LocalDateTime.now())
+ .build();
+
+ // When
+ LoginLog savedLog = loginLogRepository.save(failedLog);
+
+ // Then
+ assertThat(savedLog.getId()).isNotNull();
+ assertThat(savedLog.getUsername()).isEqualTo("testuser");
+ assertThat(savedLog.getSuccess()).isFalse();
+ assertThat(savedLog.getIpAddress()).isEqualTo("192.168.1.2");
+ assertThat(savedLog.getUserAgent()).isEqualTo("Chrome Test");
+ }
+
+ @Test
+ @Transactional
+ void deleteByUsername_WhenUserHasLogs_ShouldDeleteAllUserLogs() {
+ // Given
+ LoginLog log1 = LoginLog.builder()
+ .username("testuser")
+ .success(true)
+ .ipAddress("192.168.1.1")
+ .userAgent("Browser 1")
+ .createdAt(LocalDateTime.now())
+ .build();
+
+ LoginLog log2 = LoginLog.builder()
+ .username("testuser")
+ .success(false)
+ .ipAddress("192.168.1.2")
+ .userAgent("Browser 2")
+ .createdAt(LocalDateTime.now())
+ .build();
+
+ LoginLog anotherUserLog = LoginLog.builder()
+ .username("anotheruser")
+ .success(true)
+ .ipAddress("192.168.1.3")
+ .userAgent("Browser 3")
+ .createdAt(LocalDateTime.now())
+ .build();
+
+ entityManager.persistAndFlush(log1);
+ entityManager.persistAndFlush(log2);
+ entityManager.persistAndFlush(anotherUserLog);
+
+ // When
+ loginLogRepository.deleteByUsername("testuser");
+ entityManager.flush();
+
+ // Then
+ List remainingLogs = loginLogRepository.findAll();
+ assertThat(remainingLogs).hasSize(1);
+ assertThat(remainingLogs.get(0).getUsername()).isEqualTo("anotheruser");
+ }
+
+ @Test
+ void findById_WhenLogExists_ShouldReturnLog() {
+ // Given
+ LoginLog savedLog = entityManager.persistAndFlush(testLoginLog);
+
+ // When
+ Optional result = loginLogRepository.findById(savedLog.getId());
+
+ // Then
+ assertThat(result).isPresent();
+ assertThat(result.get().getId()).isEqualTo(savedLog.getId());
+ assertThat(result.get().getUsername()).isEqualTo("testuser");
+ }
+
+ @Test
+ void findById_WhenLogDoesNotExist_ShouldReturnEmpty() {
+ // When
+ Optional result = loginLogRepository.findById(999L);
+
+ // Then
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ void findAll_ShouldReturnAllLogs() {
+ // Given
+ LoginLog log1 = LoginLog.builder()
+ .username("user1")
+ .success(true)
+ .ipAddress("192.168.1.1")
+ .userAgent("Browser 1")
+ .createdAt(LocalDateTime.now())
+ .build();
+
+ LoginLog log2 = LoginLog.builder()
+ .username("user2")
+ .success(false)
+ .ipAddress("192.168.1.2")
+ .userAgent("Browser 2")
+ .createdAt(LocalDateTime.now())
+ .build();
+
+ entityManager.persistAndFlush(log1);
+ entityManager.persistAndFlush(log2);
+
+ // When
+ List allLogs = loginLogRepository.findAll();
+
+ // Then
+ assertThat(allLogs).hasSize(2);
+ assertThat(allLogs).extracting("username")
+ .containsExactlyInAnyOrder("user1", "user2");
+ }
+
+ @Test
+ void count_ShouldReturnCorrectCount() {
+ // Given
+ entityManager.persistAndFlush(testLoginLog);
+
+ LoginLog secondLog = LoginLog.builder()
+ .username("user2")
+ .success(false)
+ .ipAddress("192.168.1.2")
+ .userAgent("Browser 2")
+ .createdAt(LocalDateTime.now())
+ .build();
+ entityManager.persistAndFlush(secondLog);
+
+ // When
+ long count = loginLogRepository.count();
+
+ // Then
+ assertThat(count).isEqualTo(2);
+ }
+
+ @Test
+ void save_WithNullOptionalFields_ShouldPersistSuccessfully() {
+ // Given
+ LoginLog logWithNulls = LoginLog.builder()
+ .username("testuser")
+ .success(true)
+ .ipAddress(null)
+ .userAgent(null)
+ .createdAt(LocalDateTime.now())
+ .build();
+
+ // When
+ LoginLog savedLog = loginLogRepository.save(logWithNulls);
+
+ // Then
+ assertThat(savedLog.getId()).isNotNull();
+ assertThat(savedLog.getUsername()).isEqualTo("testuser");
+ assertThat(savedLog.getSuccess()).isTrue();
+ assertThat(savedLog.getIpAddress()).isNull();
+ assertThat(savedLog.getUserAgent()).isNull();
+ }
+
+ @Test
+ void existsById_WhenLogExists_ShouldReturnTrue() {
+ // Given
+ LoginLog savedLog = entityManager.persistAndFlush(testLoginLog);
+
+ // When
+ boolean exists = loginLogRepository.existsById(savedLog.getId());
+
+ // Then
+ assertThat(exists).isTrue();
+ }
+
+ @Test
+ void existsById_WhenLogDoesNotExist_ShouldReturnFalse() {
+ // When
+ boolean exists = loginLogRepository.existsById(999L);
+
+ // Then
+ assertThat(exists).isFalse();
+ }
+}
\ No newline at end of file
diff --git a/auth-service/src/test/java/com/techtorque/auth_service/repository/PermissionRepositoryTest.java b/auth-service/src/test/java/com/techtorque/auth_service/repository/PermissionRepositoryTest.java
new file mode 100644
index 0000000..c4905f3
--- /dev/null
+++ b/auth-service/src/test/java/com/techtorque/auth_service/repository/PermissionRepositoryTest.java
@@ -0,0 +1,316 @@
+package com.techtorque.auth_service.repository;
+
+import com.techtorque.auth_service.entity.Permission;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
+import org.springframework.test.context.ActiveProfiles;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Comprehensive test class for PermissionRepository
+ * Tests all repository methods, edge cases, and database constraints
+ */
+@DataJpaTest
+@ActiveProfiles("test")
+class PermissionRepositoryTest {
+
+ @Autowired
+ private TestEntityManager entityManager;
+
+ @Autowired
+ private PermissionRepository permissionRepository;
+
+ private Permission testPermission;
+
+ @BeforeEach
+ void setUp() {
+ testPermission = Permission.builder()
+ .name("CREATE_USER")
+ .description("Permission to create new users")
+ .build();
+ }
+
+ @Test
+ void save_WhenValidPermission_ShouldPersistSuccessfully() {
+ // When
+ Permission savedPermission = permissionRepository.save(testPermission);
+
+ // Then
+ assertThat(savedPermission.getId()).isNotNull();
+ assertThat(savedPermission.getName()).isEqualTo("CREATE_USER");
+ assertThat(savedPermission.getDescription()).isEqualTo("Permission to create new users");
+ }
+
+ @Test
+ void findByName_WhenPermissionExists_ShouldReturnPermission() {
+ // Given
+ entityManager.persistAndFlush(testPermission);
+
+ // When
+ Optional result = permissionRepository.findByName("CREATE_USER");
+
+ // Then
+ assertThat(result).isPresent();
+ assertThat(result.get().getName()).isEqualTo("CREATE_USER");
+ assertThat(result.get().getDescription()).isEqualTo("Permission to create new users");
+ }
+
+ @Test
+ void findByName_WhenPermissionDoesNotExist_ShouldReturnEmpty() {
+ // When
+ Optional result = permissionRepository.findByName("NON_EXISTENT");
+
+ // Then
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ void findByName_CaseSensitive_ShouldReturnEmpty() {
+ // Given
+ entityManager.persistAndFlush(testPermission);
+
+ // When
+ Optional result = permissionRepository.findByName("create_user");
+
+ // Then
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ void findByNameIn_WhenPermissionsExist_ShouldReturnMatchingPermissions() {
+ // Given
+ Permission permission1 = Permission.builder()
+ .name("READ_USER")
+ .description("Permission to read user data")
+ .build();
+
+ Permission permission2 = Permission.builder()
+ .name("UPDATE_USER")
+ .description("Permission to update user data")
+ .build();
+
+ Permission permission3 = Permission.builder()
+ .name("DELETE_USER")
+ .description("Permission to delete users")
+ .build();
+
+ entityManager.persistAndFlush(testPermission);
+ entityManager.persistAndFlush(permission1);
+ entityManager.persistAndFlush(permission2);
+ entityManager.persistAndFlush(permission3);
+
+ // When
+ Set names = Set.of("CREATE_USER", "READ_USER", "NON_EXISTENT");
+ Set result = permissionRepository.findByNameIn(names);
+
+ // Then
+ assertThat(result).hasSize(2);
+ assertThat(result).extracting("name")
+ .containsExactlyInAnyOrder("CREATE_USER", "READ_USER");
+ }
+
+ @Test
+ void findByNameIn_WhenEmptySet_ShouldReturnEmptySet() {
+ // Given
+ entityManager.persistAndFlush(testPermission);
+
+ // When
+ Set result = permissionRepository.findByNameIn(Set.of());
+
+ // Then
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ void findByNameIn_WhenNoMatches_ShouldReturnEmptySet() {
+ // Given
+ entityManager.persistAndFlush(testPermission);
+
+ // When
+ Set names = Set.of("NON_EXISTENT1", "NON_EXISTENT2");
+ Set result = permissionRepository.findByNameIn(names);
+
+ // Then
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ void existsByName_WhenPermissionExists_ShouldReturnTrue() {
+ // Given
+ entityManager.persistAndFlush(testPermission);
+
+ // When
+ boolean exists = permissionRepository.existsByName("CREATE_USER");
+
+ // Then
+ assertThat(exists).isTrue();
+ }
+
+ @Test
+ void existsByName_WhenPermissionDoesNotExist_ShouldReturnFalse() {
+ // When
+ boolean exists = permissionRepository.existsByName("NON_EXISTENT");
+
+ // Then
+ assertThat(exists).isFalse();
+ }
+
+ @Test
+ void findById_WhenPermissionExists_ShouldReturnPermission() {
+ // Given
+ Permission savedPermission = entityManager.persistAndFlush(testPermission);
+
+ // When
+ Optional result = permissionRepository.findById(savedPermission.getId());
+
+ // Then
+ assertThat(result).isPresent();
+ assertThat(result.get().getId()).isEqualTo(savedPermission.getId());
+ assertThat(result.get().getName()).isEqualTo("CREATE_USER");
+ }
+
+ @Test
+ void findById_WhenPermissionDoesNotExist_ShouldReturnEmpty() {
+ // When
+ Optional result = permissionRepository.findById(999L);
+
+ // Then
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ void findAll_ShouldReturnAllPermissions() {
+ // Given
+ Permission permission1 = Permission.builder()
+ .name("READ_REPORTS")
+ .description("Permission to read reports")
+ .build();
+
+ Permission permission2 = Permission.builder()
+ .name("WRITE_REPORTS")
+ .description("Permission to write reports")
+ .build();
+
+ entityManager.persistAndFlush(permission1);
+ entityManager.persistAndFlush(permission2);
+
+ // When
+ List allPermissions = permissionRepository.findAll();
+
+ // Then
+ assertThat(allPermissions).hasSize(2);
+ assertThat(allPermissions).extracting("name")
+ .containsExactlyInAnyOrder("READ_REPORTS", "WRITE_REPORTS");
+ }
+
+ @Test
+ void count_ShouldReturnCorrectCount() {
+ // Given
+ entityManager.persistAndFlush(testPermission);
+
+ Permission secondPermission = Permission.builder()
+ .name("DELETE_USER")
+ .description("Permission to delete users")
+ .build();
+ entityManager.persistAndFlush(secondPermission);
+
+ // When
+ long count = permissionRepository.count();
+
+ // Then
+ assertThat(count).isEqualTo(2);
+ }
+
+ @Test
+ void update_WhenPermissionExists_ShouldUpdateSuccessfully() {
+ // Given
+ Permission savedPermission = entityManager.persistAndFlush(testPermission);
+ entityManager.detach(savedPermission);
+
+ // When
+ savedPermission.setDescription("Updated description for creating users");
+ Permission updatedPermission = permissionRepository.save(savedPermission);
+
+ // Then
+ assertThat(updatedPermission.getDescription()).isEqualTo("Updated description for creating users");
+ assertThat(updatedPermission.getName()).isEqualTo("CREATE_USER"); // Should remain unchanged
+ }
+
+ @Test
+ void deleteById_WhenPermissionExists_ShouldRemovePermission() {
+ // Given
+ Permission savedPermission = entityManager.persistAndFlush(testPermission);
+ Long permissionId = savedPermission.getId();
+
+ // When
+ permissionRepository.deleteById(permissionId);
+ entityManager.flush();
+
+ // Then
+ Optional result = permissionRepository.findById(permissionId);
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ void save_WithNullDescription_ShouldPersistSuccessfully() {
+ // Given
+ Permission permissionWithoutDescription = Permission.builder()
+ .name("SIMPLE_PERMISSION")
+ .description(null)
+ .build();
+
+ // When
+ Permission savedPermission = permissionRepository.save(permissionWithoutDescription);
+
+ // Then
+ assertThat(savedPermission.getId()).isNotNull();
+ assertThat(savedPermission.getName()).isEqualTo("SIMPLE_PERMISSION");
+ assertThat(savedPermission.getDescription()).isNull();
+ }
+
+ @Test
+ void save_WithEmptyDescription_ShouldPersistSuccessfully() {
+ // Given
+ Permission permissionWithEmptyDescription = Permission.builder()
+ .name("ANOTHER_PERMISSION")
+ .description("")
+ .build();
+
+ // When
+ Permission savedPermission = permissionRepository.save(permissionWithEmptyDescription);
+
+ // Then
+ assertThat(savedPermission.getId()).isNotNull();
+ assertThat(savedPermission.getName()).isEqualTo("ANOTHER_PERMISSION");
+ assertThat(savedPermission.getDescription()).isEmpty();
+ }
+
+ @Test
+ void existsById_WhenPermissionExists_ShouldReturnTrue() {
+ // Given
+ Permission savedPermission = entityManager.persistAndFlush(testPermission);
+
+ // When
+ boolean exists = permissionRepository.existsById(savedPermission.getId());
+
+ // Then
+ assertThat(exists).isTrue();
+ }
+
+ @Test
+ void existsById_WhenPermissionDoesNotExist_ShouldReturnFalse() {
+ // When
+ boolean exists = permissionRepository.existsById(999L);
+
+ // Then
+ assertThat(exists).isFalse();
+ }
+}
\ No newline at end of file
diff --git a/auth-service/src/test/java/com/techtorque/auth_service/repository/RefreshTokenRepositoryTest.java b/auth-service/src/test/java/com/techtorque/auth_service/repository/RefreshTokenRepositoryTest.java
new file mode 100644
index 0000000..8990ed3
--- /dev/null
+++ b/auth-service/src/test/java/com/techtorque/auth_service/repository/RefreshTokenRepositoryTest.java
@@ -0,0 +1,357 @@
+package com.techtorque.auth_service.repository;
+
+import com.techtorque.auth_service.entity.RefreshToken;
+import com.techtorque.auth_service.entity.Role;
+import com.techtorque.auth_service.entity.RoleName;
+import com.techtorque.auth_service.entity.User;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.Optional;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Comprehensive test class for RefreshTokenRepository
+ * Tests all repository methods, edge cases, and database constraints
+ */
+@DataJpaTest
+@ActiveProfiles("test")
+class RefreshTokenRepositoryTest {
+
+ @Autowired
+ private TestEntityManager entityManager;
+
+ @Autowired
+ private RefreshTokenRepository refreshTokenRepository;
+
+ private User testUser;
+ private RefreshToken testToken;
+
+ @BeforeEach
+ void setUp() {
+ // Create test role
+ Role testRole = Role.builder()
+ .name(RoleName.CUSTOMER)
+ .description("Test customer role")
+ .build();
+ testRole = entityManager.persistAndFlush(testRole);
+
+ // Create test user
+ testUser = User.builder()
+ .username("testuser")
+ .password("encodedPassword")
+ .email("test@example.com")
+ .fullName("Test User")
+ .enabled(true)
+ .emailVerified(false)
+ .createdAt(LocalDateTime.now())
+ .build();
+ testUser.addRole(testRole);
+ testUser = entityManager.persistAndFlush(testUser);
+
+ // Create test refresh token
+ testToken = RefreshToken.builder()
+ .token("test-refresh-token")
+ .user(testUser)
+ .expiryDate(LocalDateTime.now().plusDays(7))
+ .createdAt(LocalDateTime.now())
+ .ipAddress("192.168.1.1")
+ .userAgent("Test User Agent")
+ .build();
+ }
+
+ @Test
+ void save_WhenValidToken_ShouldPersistSuccessfully() {
+ // When
+ RefreshToken savedToken = refreshTokenRepository.save(testToken);
+
+ // Then
+ assertThat(savedToken.getId()).isNotNull();
+ assertThat(savedToken.getToken()).isEqualTo("test-refresh-token");
+ assertThat(savedToken.getUser().getId()).isEqualTo(testUser.getId());
+ assertThat(savedToken.getExpiryDate()).isNotNull();
+ assertThat(savedToken.getCreatedAt()).isNotNull();
+ assertThat(savedToken.getIpAddress()).isEqualTo("192.168.1.1");
+ assertThat(savedToken.getUserAgent()).isEqualTo("Test User Agent");
+ assertThat(savedToken.getRevokedAt()).isNull();
+ }
+
+ @Test
+ void findByToken_WhenTokenExists_ShouldReturnToken() {
+ // Given
+ entityManager.persistAndFlush(testToken);
+
+ // When
+ Optional result = refreshTokenRepository.findByToken("test-refresh-token");
+
+ // Then
+ assertThat(result).isPresent();
+ assertThat(result.get().getToken()).isEqualTo("test-refresh-token");
+ assertThat(result.get().getUser().getId()).isEqualTo(testUser.getId());
+ assertThat(result.get().getIpAddress()).isEqualTo("192.168.1.1");
+ }
+
+ @Test
+ void findByToken_WhenTokenDoesNotExist_ShouldReturnEmpty() {
+ // When
+ Optional result = refreshTokenRepository.findByToken("non-existent-token");
+
+ // Then
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ @Transactional
+ void deleteByUser_WhenUserHasTokens_ShouldDeleteAllUserTokens() {
+ // Given
+ RefreshToken token1 = RefreshToken.builder()
+ .token("token-1")
+ .user(testUser)
+ .expiryDate(LocalDateTime.now().plusDays(7))
+ .createdAt(LocalDateTime.now())
+ .build();
+
+ RefreshToken token2 = RefreshToken.builder()
+ .token("token-2")
+ .user(testUser)
+ .expiryDate(LocalDateTime.now().plusDays(7))
+ .createdAt(LocalDateTime.now())
+ .build();
+
+ entityManager.persistAndFlush(token1);
+ entityManager.persistAndFlush(token2);
+
+ // Create another user with a token to ensure we only delete current user's
+ // tokens
+ User anotherUser = User.builder()
+ .username("anotheruser")
+ .password("password")
+ .email("another@example.com")
+ .enabled(true)
+ .emailVerified(false)
+ .createdAt(LocalDateTime.now())
+ .build();
+ anotherUser = entityManager.persistAndFlush(anotherUser);
+
+ RefreshToken anotherUserToken = RefreshToken.builder()
+ .token("another-token")
+ .user(anotherUser)
+ .expiryDate(LocalDateTime.now().plusDays(7))
+ .createdAt(LocalDateTime.now())
+ .build();
+ entityManager.persistAndFlush(anotherUserToken);
+
+ // When
+ refreshTokenRepository.deleteByUser(testUser);
+ entityManager.flush();
+
+ // Then
+ assertThat(refreshTokenRepository.findByToken("token-1")).isEmpty();
+ assertThat(refreshTokenRepository.findByToken("token-2")).isEmpty();
+ assertThat(refreshTokenRepository.findByToken("another-token")).isPresent();
+ }
+
+ @Test
+ @Transactional
+ void deleteExpiredTokens_WhenExpiredTokensExist_ShouldDeleteOnlyExpiredTokens() {
+ // Given
+ RefreshToken expiredToken1 = RefreshToken.builder()
+ .token("expired-token-1")
+ .user(testUser)
+ .expiryDate(LocalDateTime.now().minusDays(1)) // Expired
+ .createdAt(LocalDateTime.now().minusDays(2))
+ .build();
+
+ RefreshToken expiredToken2 = RefreshToken.builder()
+ .token("expired-token-2")
+ .user(testUser)
+ .expiryDate(LocalDateTime.now().minusHours(1)) // Expired
+ .createdAt(LocalDateTime.now().minusDays(1))
+ .build();
+
+ RefreshToken validToken = RefreshToken.builder()
+ .token("valid-token")
+ .user(testUser)
+ .expiryDate(LocalDateTime.now().plusDays(7)) // Not expired
+ .createdAt(LocalDateTime.now())
+ .build();
+
+ entityManager.persistAndFlush(expiredToken1);
+ entityManager.persistAndFlush(expiredToken2);
+ entityManager.persistAndFlush(validToken);
+
+ // When
+ refreshTokenRepository.deleteExpiredTokens(LocalDateTime.now());
+ entityManager.flush();
+
+ // Then
+ assertThat(refreshTokenRepository.findByToken("expired-token-1")).isEmpty();
+ assertThat(refreshTokenRepository.findByToken("expired-token-2")).isEmpty();
+ assertThat(refreshTokenRepository.findByToken("valid-token")).isPresent();
+ }
+
+ @Test
+ void isExpired_WhenTokenIsExpired_ShouldReturnTrue() {
+ // Given
+ RefreshToken expiredToken = RefreshToken.builder()
+ .token("expired-token")
+ .user(testUser)
+ .expiryDate(LocalDateTime.now().minusDays(1))
+ .createdAt(LocalDateTime.now().minusDays(2))
+ .build();
+
+ // When
+ boolean isExpired = expiredToken.isExpired();
+
+ // Then
+ assertThat(isExpired).isTrue();
+ }
+
+ @Test
+ void isExpired_WhenTokenIsNotExpired_ShouldReturnFalse() {
+ // Given
+ RefreshToken validToken = RefreshToken.builder()
+ .token("valid-token")
+ .user(testUser)
+ .expiryDate(LocalDateTime.now().plusDays(7))
+ .createdAt(LocalDateTime.now())
+ .build();
+
+ // When
+ boolean isExpired = validToken.isExpired();
+
+ // Then
+ assertThat(isExpired).isFalse();
+ }
+
+ @Test
+ void isRevoked_WhenTokenIsRevoked_ShouldReturnTrue() {
+ // Given
+ RefreshToken revokedToken = RefreshToken.builder()
+ .token("revoked-token")
+ .user(testUser)
+ .expiryDate(LocalDateTime.now().plusDays(7))
+ .createdAt(LocalDateTime.now())
+ .revokedAt(LocalDateTime.now())
+ .build();
+
+ // When
+ boolean isRevoked = revokedToken.isRevoked();
+
+ // Then
+ assertThat(isRevoked).isTrue();
+ }
+
+ @Test
+ void isRevoked_WhenTokenIsNotRevoked_ShouldReturnFalse() {
+ // Given
+ RefreshToken activeToken = RefreshToken.builder()
+ .token("active-token")
+ .user(testUser)
+ .expiryDate(LocalDateTime.now().plusDays(7))
+ .createdAt(LocalDateTime.now())
+ .revokedAt(null)
+ .build();
+
+ // When
+ boolean isRevoked = activeToken.isRevoked();
+
+ // Then
+ assertThat(isRevoked).isFalse();
+ }
+
+ @Test
+ void findById_WhenTokenExists_ShouldReturnToken() {
+ // Given
+ RefreshToken savedToken = entityManager.persistAndFlush(testToken);
+
+ // When
+ Optional result = refreshTokenRepository.findById(savedToken.getId());
+
+ // Then
+ assertThat(result).isPresent();
+ assertThat(result.get().getId()).isEqualTo(savedToken.getId());
+ assertThat(result.get().getToken()).isEqualTo("test-refresh-token");
+ }
+
+ @Test
+ void findById_WhenTokenDoesNotExist_ShouldReturnEmpty() {
+ // When
+ Optional result = refreshTokenRepository.findById("non-existent-id");
+
+ // Then
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ void update_WhenTokenIsRevoked_ShouldUpdateRevokedAt() {
+ // Given
+ RefreshToken savedToken = entityManager.persistAndFlush(testToken);
+ entityManager.detach(savedToken);
+
+ // When
+ LocalDateTime revokedTime = LocalDateTime.now();
+ savedToken.setRevokedAt(revokedTime);
+ RefreshToken updatedToken = refreshTokenRepository.save(savedToken);
+
+ // Then
+ assertThat(updatedToken.getRevokedAt()).isEqualTo(revokedTime);
+ assertThat(updatedToken.isRevoked()).isTrue();
+ }
+
+ @Test
+ void findAll_ShouldReturnAllTokens() {
+ // Given
+ RefreshToken token1 = RefreshToken.builder()
+ .token("token-1")
+ .user(testUser)
+ .expiryDate(LocalDateTime.now().plusDays(7))
+ .createdAt(LocalDateTime.now())
+ .build();
+
+ RefreshToken token2 = RefreshToken.builder()
+ .token("token-2")
+ .user(testUser)
+ .expiryDate(LocalDateTime.now().plusDays(7))
+ .createdAt(LocalDateTime.now())
+ .build();
+
+ entityManager.persistAndFlush(token1);
+ entityManager.persistAndFlush(token2);
+
+ // When
+ var allTokens = refreshTokenRepository.findAll();
+
+ // Then
+ assertThat(allTokens).hasSize(2);
+ assertThat(allTokens).extracting("token")
+ .containsExactlyInAnyOrder("token-1", "token-2");
+ }
+
+ @Test
+ void count_ShouldReturnCorrectCount() {
+ // Given
+ entityManager.persistAndFlush(testToken);
+
+ RefreshToken secondToken = RefreshToken.builder()
+ .token("second-token")
+ .user(testUser)
+ .expiryDate(LocalDateTime.now().plusDays(7))
+ .createdAt(LocalDateTime.now())
+ .build();
+ entityManager.persistAndFlush(secondToken);
+
+ // When
+ long count = refreshTokenRepository.count();
+
+ // Then
+ assertThat(count).isEqualTo(2);
+ }
+}
\ No newline at end of file
diff --git a/auth-service/src/test/java/com/techtorque/auth_service/repository/RoleRepositoryTest.java b/auth-service/src/test/java/com/techtorque/auth_service/repository/RoleRepositoryTest.java
new file mode 100644
index 0000000..4d35dbd
--- /dev/null
+++ b/auth-service/src/test/java/com/techtorque/auth_service/repository/RoleRepositoryTest.java
@@ -0,0 +1,268 @@
+package com.techtorque.auth_service.repository;
+
+import com.techtorque.auth_service.entity.Role;
+import com.techtorque.auth_service.entity.RoleName;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
+import org.springframework.test.context.ActiveProfiles;
+
+import java.util.Optional;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Comprehensive test class for RoleRepository
+ * Tests all repository methods, edge cases, and database constraints
+ */
+@DataJpaTest
+@ActiveProfiles("test")
+class RoleRepositoryTest {
+
+ @Autowired
+ private TestEntityManager entityManager;
+
+ @Autowired
+ private RoleRepository roleRepository;
+
+ private Role testRole;
+
+ @BeforeEach
+ void setUp() {
+ testRole = Role.builder()
+ .name(RoleName.ADMIN)
+ .description("Administrator role with full access")
+ .build();
+ }
+
+ @Test
+ void findByName_WhenRoleExists_ShouldReturnRole() {
+ // Given
+ entityManager.persistAndFlush(testRole);
+
+ // When
+ Optional result = roleRepository.findByName(RoleName.ADMIN);
+
+ // Then
+ assertThat(result).isPresent();
+ assertThat(result.get().getName()).isEqualTo(RoleName.ADMIN);
+ assertThat(result.get().getDescription()).isEqualTo("Administrator role with full access");
+ }
+
+ @Test
+ void findByName_WhenRoleDoesNotExist_ShouldReturnEmpty() {
+ // When
+ Optional result = roleRepository.findByName(RoleName.SUPER_ADMIN);
+
+ // Then
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ void existsByName_WhenRoleExists_ShouldReturnTrue() {
+ // Given
+ entityManager.persistAndFlush(testRole);
+
+ // When
+ boolean exists = roleRepository.existsByName(RoleName.ADMIN);
+
+ // Then
+ assertThat(exists).isTrue();
+ }
+
+ @Test
+ void existsByName_WhenRoleDoesNotExist_ShouldReturnFalse() {
+ // When
+ boolean exists = roleRepository.existsByName(RoleName.CUSTOMER);
+
+ // Then
+ assertThat(exists).isFalse();
+ }
+
+ @Test
+ void save_WhenValidRole_ShouldPersistSuccessfully() {
+ // Given
+ Role newRole = Role.builder()
+ .name(RoleName.EMPLOYEE)
+ .description("Employee role with limited access")
+ .build();
+
+ // When
+ Role savedRole = roleRepository.save(newRole);
+
+ // Then
+ assertThat(savedRole.getId()).isNotNull();
+ assertThat(savedRole.getName()).isEqualTo(RoleName.EMPLOYEE);
+ assertThat(savedRole.getDescription()).isEqualTo("Employee role with limited access");
+ }
+
+ @Test
+ void save_AllRoleNames_ShouldPersistAllEnumValues() {
+ // Given & When & Then
+ for (RoleName roleName : RoleName.values()) {
+ Role role = Role.builder()
+ .name(roleName)
+ .description(roleName.name() + " role")
+ .build();
+
+ Role savedRole = roleRepository.save(role);
+
+ assertThat(savedRole.getId()).isNotNull();
+ assertThat(savedRole.getName()).isEqualTo(roleName);
+ assertThat(savedRole.getDescription()).isEqualTo(roleName.name() + " role");
+ }
+ }
+
+ @Test
+ void findById_WhenRoleExists_ShouldReturnRole() {
+ // Given
+ Role savedRole = entityManager.persistAndFlush(testRole);
+
+ // When
+ Optional result = roleRepository.findById(savedRole.getId());
+
+ // Then
+ assertThat(result).isPresent();
+ assertThat(result.get().getId()).isEqualTo(savedRole.getId());
+ assertThat(result.get().getName()).isEqualTo(RoleName.ADMIN);
+ }
+
+ @Test
+ void findById_WhenRoleDoesNotExist_ShouldReturnEmpty() {
+ // When
+ Optional result = roleRepository.findById(999L);
+
+ // Then
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ void deleteById_WhenRoleExists_ShouldRemoveRole() {
+ // Given
+ Role savedRole = entityManager.persistAndFlush(testRole);
+ Long roleId = savedRole.getId();
+
+ // When
+ roleRepository.deleteById(roleId);
+ entityManager.flush();
+
+ // Then
+ Optional result = roleRepository.findById(roleId);
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ void findAll_ShouldReturnAllRoles() {
+ // Given
+ Role adminRole = Role.builder()
+ .name(RoleName.ADMIN)
+ .description("Admin role")
+ .build();
+
+ Role customerRole = Role.builder()
+ .name(RoleName.CUSTOMER)
+ .description("Customer role")
+ .build();
+
+ entityManager.persistAndFlush(adminRole);
+ entityManager.persistAndFlush(customerRole);
+
+ // When
+ var allRoles = roleRepository.findAll();
+
+ // Then
+ assertThat(allRoles).hasSize(2);
+ assertThat(allRoles).extracting("name")
+ .containsExactlyInAnyOrder(RoleName.ADMIN, RoleName.CUSTOMER);
+ }
+
+ @Test
+ void count_ShouldReturnCorrectCount() {
+ // Given
+ entityManager.persistAndFlush(testRole);
+
+ Role secondRole = Role.builder()
+ .name(RoleName.EMPLOYEE)
+ .description("Employee role")
+ .build();
+ entityManager.persistAndFlush(secondRole);
+
+ // When
+ long count = roleRepository.count();
+
+ // Then
+ assertThat(count).isEqualTo(2);
+ }
+
+ @Test
+ void update_WhenRoleExists_ShouldUpdateSuccessfully() {
+ // Given
+ Role savedRole = entityManager.persistAndFlush(testRole);
+ entityManager.detach(savedRole);
+
+ // When
+ savedRole.setDescription("Updated admin role description");
+ Role updatedRole = roleRepository.save(savedRole);
+
+ // Then
+ assertThat(updatedRole.getDescription()).isEqualTo("Updated admin role description");
+ assertThat(updatedRole.getName()).isEqualTo(RoleName.ADMIN); // Should remain unchanged
+ }
+
+ @Test
+ void existsById_WhenRoleExists_ShouldReturnTrue() {
+ // Given
+ Role savedRole = entityManager.persistAndFlush(testRole);
+
+ // When
+ boolean exists = roleRepository.existsById(savedRole.getId());
+
+ // Then
+ assertThat(exists).isTrue();
+ }
+
+ @Test
+ void existsById_WhenRoleDoesNotExist_ShouldReturnFalse() {
+ // When
+ boolean exists = roleRepository.existsById(999L);
+
+ // Then
+ assertThat(exists).isFalse();
+ }
+
+ @Test
+ void save_WithNullDescription_ShouldPersistSuccessfully() {
+ // Given
+ Role roleWithoutDescription = Role.builder()
+ .name(RoleName.CUSTOMER)
+ .description(null)
+ .build();
+
+ // When
+ Role savedRole = roleRepository.save(roleWithoutDescription);
+
+ // Then
+ assertThat(savedRole.getId()).isNotNull();
+ assertThat(savedRole.getName()).isEqualTo(RoleName.CUSTOMER);
+ assertThat(savedRole.getDescription()).isNull();
+ }
+
+ @Test
+ void save_WithEmptyDescription_ShouldPersistSuccessfully() {
+ // Given
+ Role roleWithEmptyDescription = Role.builder()
+ .name(RoleName.CUSTOMER)
+ .description("")
+ .build();
+
+ // When
+ Role savedRole = roleRepository.save(roleWithEmptyDescription);
+
+ // Then
+ assertThat(savedRole.getId()).isNotNull();
+ assertThat(savedRole.getName()).isEqualTo(RoleName.CUSTOMER);
+ assertThat(savedRole.getDescription()).isEmpty();
+ }
+}
\ No newline at end of file
diff --git a/auth-service/src/test/java/com/techtorque/auth_service/repository/UserPreferencesRepositoryTest.java b/auth-service/src/test/java/com/techtorque/auth_service/repository/UserPreferencesRepositoryTest.java
new file mode 100644
index 0000000..3feb79a
--- /dev/null
+++ b/auth-service/src/test/java/com/techtorque/auth_service/repository/UserPreferencesRepositoryTest.java
@@ -0,0 +1,395 @@
+package com.techtorque.auth_service.repository;
+
+import com.techtorque.auth_service.entity.Role;
+import com.techtorque.auth_service.entity.RoleName;
+import com.techtorque.auth_service.entity.User;
+import com.techtorque.auth_service.entity.UserPreferences;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Optional;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Comprehensive test class for UserPreferencesRepository
+ * Tests all repository methods, edge cases, and database constraints
+ */
+@DataJpaTest
+@ActiveProfiles("test")
+class UserPreferencesRepositoryTest {
+
+ @Autowired
+ private TestEntityManager entityManager;
+
+ @Autowired
+ private UserPreferencesRepository userPreferencesRepository;
+
+ private User testUser;
+ private UserPreferences testPreferences;
+
+ @BeforeEach
+ void setUp() {
+ // Create test role
+ Role testRole = Role.builder()
+ .name(RoleName.CUSTOMER)
+ .description("Test customer role")
+ .build();
+ testRole = entityManager.persistAndFlush(testRole);
+
+ // Create test user
+ testUser = User.builder()
+ .username("testuser")
+ .password("encodedPassword")
+ .email("test@example.com")
+ .fullName("Test User")
+ .enabled(true)
+ .emailVerified(false)
+ .createdAt(LocalDateTime.now())
+ .build();
+ testUser.addRole(testRole);
+ testUser = entityManager.persistAndFlush(testUser);
+
+ // Create test user preferences
+ testPreferences = UserPreferences.builder()
+ .user(testUser)
+ .emailNotifications(true)
+ .smsNotifications(false)
+ .pushNotifications(true)
+ .language("en")
+ .appointmentReminders(true)
+ .serviceUpdates(true)
+ .marketingEmails(false)
+ .build();
+ }
+
+ @Test
+ void save_WhenValidPreferences_ShouldPersistSuccessfully() {
+ // When
+ UserPreferences savedPreferences = userPreferencesRepository.save(testPreferences);
+
+ // Then
+ assertThat(savedPreferences.getId()).isNotNull();
+ assertThat(savedPreferences.getUser().getId()).isEqualTo(testUser.getId());
+ assertThat(savedPreferences.getEmailNotifications()).isTrue();
+ assertThat(savedPreferences.getSmsNotifications()).isFalse();
+ assertThat(savedPreferences.getPushNotifications()).isTrue();
+ assertThat(savedPreferences.getLanguage()).isEqualTo("en");
+ assertThat(savedPreferences.getAppointmentReminders()).isTrue();
+ assertThat(savedPreferences.getServiceUpdates()).isTrue();
+ assertThat(savedPreferences.getMarketingEmails()).isFalse();
+ }
+
+ @Test
+ void save_WithDefaults_ShouldUseDefaultValues() {
+ // Given
+ UserPreferences preferencesWithDefaults = UserPreferences.builder()
+ .user(testUser)
+ .build();
+
+ // When
+ UserPreferences savedPreferences = userPreferencesRepository.save(preferencesWithDefaults);
+
+ // Then
+ assertThat(savedPreferences.getId()).isNotNull();
+ assertThat(savedPreferences.getEmailNotifications()).isTrue(); // Default
+ assertThat(savedPreferences.getSmsNotifications()).isFalse(); // Default
+ assertThat(savedPreferences.getPushNotifications()).isTrue(); // Default
+ assertThat(savedPreferences.getLanguage()).isEqualTo("en"); // Default
+ assertThat(savedPreferences.getAppointmentReminders()).isTrue(); // Default
+ assertThat(savedPreferences.getServiceUpdates()).isTrue(); // Default
+ assertThat(savedPreferences.getMarketingEmails()).isFalse(); // Default
+ }
+
+ @Test
+ void findByUser_WhenPreferencesExist_ShouldReturnPreferences() {
+ // Given
+ entityManager.persistAndFlush(testPreferences);
+
+ // When
+ Optional result = userPreferencesRepository.findByUser(testUser);
+
+ // Then
+ assertThat(result).isPresent();
+ assertThat(result.get().getUser().getId()).isEqualTo(testUser.getId());
+ assertThat(result.get().getEmailNotifications()).isTrue();
+ assertThat(result.get().getLanguage()).isEqualTo("en");
+ }
+
+ @Test
+ void findByUser_WhenPreferencesDoNotExist_ShouldReturnEmpty() {
+ // Given
+ User anotherUser = User.builder()
+ .username("anotheruser")
+ .password("password")
+ .email("another@example.com")
+ .enabled(true)
+ .emailVerified(false)
+ .createdAt(LocalDateTime.now())
+ .build();
+ anotherUser = entityManager.persistAndFlush(anotherUser);
+
+ // When
+ Optional result = userPreferencesRepository.findByUser(anotherUser);
+
+ // Then
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ @Transactional
+ void deleteByUser_WhenPreferencesExist_ShouldDeleteUserPreferences() {
+ // Given
+ entityManager.persistAndFlush(testPreferences);
+
+ // Create another user with preferences to ensure we only delete current user's
+ // preferences
+ User anotherUser = User.builder()
+ .username("anotheruser")
+ .password("password")
+ .email("another@example.com")
+ .enabled(true)
+ .emailVerified(false)
+ .createdAt(LocalDateTime.now())
+ .build();
+ anotherUser = entityManager.persistAndFlush(anotherUser);
+
+ UserPreferences anotherPreferences = UserPreferences.builder()
+ .user(anotherUser)
+ .emailNotifications(false)
+ .language("fr")
+ .build();
+ entityManager.persistAndFlush(anotherPreferences);
+
+ // When
+ userPreferencesRepository.deleteByUser(testUser);
+ entityManager.flush();
+
+ // Then
+ Optional deletedPreferences = userPreferencesRepository.findByUser(testUser);
+ Optional remainingPreferences = userPreferencesRepository.findByUser(anotherUser);
+
+ assertThat(deletedPreferences).isEmpty();
+ assertThat(remainingPreferences).isPresent();
+ }
+
+ @Test
+ void update_WhenPreferencesExist_ShouldUpdateSuccessfully() {
+ // Given
+ UserPreferences savedPreferences = entityManager.persistAndFlush(testPreferences);
+ entityManager.detach(savedPreferences);
+
+ // When
+ savedPreferences.setEmailNotifications(false);
+ savedPreferences.setSmsNotifications(true);
+ savedPreferences.setLanguage("es");
+ savedPreferences.setMarketingEmails(true);
+ UserPreferences updatedPreferences = userPreferencesRepository.save(savedPreferences);
+
+ // Then
+ assertThat(updatedPreferences.getEmailNotifications()).isFalse();
+ assertThat(updatedPreferences.getSmsNotifications()).isTrue();
+ assertThat(updatedPreferences.getLanguage()).isEqualTo("es");
+ assertThat(updatedPreferences.getMarketingEmails()).isTrue();
+ assertThat(updatedPreferences.getUser().getId()).isEqualTo(testUser.getId()); // Should remain unchanged
+ }
+
+ @Test
+ void findById_WhenPreferencesExist_ShouldReturnPreferences() {
+ // Given
+ UserPreferences savedPreferences = entityManager.persistAndFlush(testPreferences);
+
+ // When
+ Optional result = userPreferencesRepository.findById(savedPreferences.getId());
+
+ // Then
+ assertThat(result).isPresent();
+ assertThat(result.get().getId()).isEqualTo(savedPreferences.getId());
+ assertThat(result.get().getUser().getId()).isEqualTo(testUser.getId());
+ }
+
+ @Test
+ void findById_WhenPreferencesDoNotExist_ShouldReturnEmpty() {
+ // When
+ Optional result = userPreferencesRepository.findById("non-existent-id");
+
+ // Then
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ void findAll_ShouldReturnAllPreferences() {
+ // Given
+ User user1 = User.builder()
+ .username("user1")
+ .password("password")
+ .email("user1@example.com")
+ .enabled(true)
+ .emailVerified(false)
+ .createdAt(LocalDateTime.now())
+ .build();
+ user1 = entityManager.persistAndFlush(user1);
+
+ User user2 = User.builder()
+ .username("user2")
+ .password("password")
+ .email("user2@example.com")
+ .enabled(true)
+ .emailVerified(false)
+ .createdAt(LocalDateTime.now())
+ .build();
+ user2 = entityManager.persistAndFlush(user2);
+
+ UserPreferences prefs1 = UserPreferences.builder()
+ .user(user1)
+ .language("en")
+ .emailNotifications(true)
+ .build();
+
+ UserPreferences prefs2 = UserPreferences.builder()
+ .user(user2)
+ .language("fr")
+ .emailNotifications(false)
+ .build();
+
+ entityManager.persistAndFlush(prefs1);
+ entityManager.persistAndFlush(prefs2);
+
+ // When
+ List allPreferences = userPreferencesRepository.findAll();
+
+ // Then
+ assertThat(allPreferences).hasSize(2);
+ assertThat(allPreferences).extracting("language")
+ .containsExactlyInAnyOrder("en", "fr");
+ }
+
+ @Test
+ void count_ShouldReturnCorrectCount() {
+ // Given
+ entityManager.persistAndFlush(testPreferences);
+
+ User anotherUser = User.builder()
+ .username("user2")
+ .password("password")
+ .email("user2@example.com")
+ .enabled(true)
+ .emailVerified(false)
+ .createdAt(LocalDateTime.now())
+ .build();
+ anotherUser = entityManager.persistAndFlush(anotherUser);
+
+ UserPreferences secondPreferences = UserPreferences.builder()
+ .user(anotherUser)
+ .language("fr")
+ .build();
+ entityManager.persistAndFlush(secondPreferences);
+
+ // When
+ long count = userPreferencesRepository.count();
+
+ // Then
+ assertThat(count).isEqualTo(2);
+ }
+
+ @Test
+ void save_WithDifferentLanguages_ShouldPersistSuccessfully() {
+ // Given
+ String[] languages = { "en", "es", "fr", "de", "it", "pt", "ja", "zh", "ko", "ar" };
+
+ for (int i = 0; i < languages.length; i++) {
+ User user = User.builder()
+ .username("user" + i)
+ .password("password")
+ .email("user" + i + "@example.com")
+ .enabled(true)
+ .emailVerified(false)
+ .createdAt(LocalDateTime.now())
+ .build();
+ user = entityManager.persistAndFlush(user);
+
+ UserPreferences preferences = UserPreferences.builder()
+ .user(user)
+ .language(languages[i])
+ .build();
+
+ // When
+ UserPreferences savedPreferences = userPreferencesRepository.save(preferences);
+
+ // Then
+ assertThat(savedPreferences.getId()).isNotNull();
+ assertThat(savedPreferences.getLanguage()).isEqualTo(languages[i]);
+ assertThat(savedPreferences.getUser().getId()).isEqualTo(user.getId());
+ }
+ }
+
+ @Test
+ void save_WithAllNotificationsDisabled_ShouldPersistSuccessfully() {
+ // Given
+ UserPreferences allDisabledPreferences = UserPreferences.builder()
+ .user(testUser)
+ .emailNotifications(false)
+ .smsNotifications(false)
+ .pushNotifications(false)
+ .appointmentReminders(false)
+ .serviceUpdates(false)
+ .marketingEmails(false)
+ .language("en")
+ .build();
+
+ // When
+ UserPreferences savedPreferences = userPreferencesRepository.save(allDisabledPreferences);
+
+ // Then
+ assertThat(savedPreferences.getId()).isNotNull();
+ assertThat(savedPreferences.getEmailNotifications()).isFalse();
+ assertThat(savedPreferences.getSmsNotifications()).isFalse();
+ assertThat(savedPreferences.getPushNotifications()).isFalse();
+ assertThat(savedPreferences.getAppointmentReminders()).isFalse();
+ assertThat(savedPreferences.getServiceUpdates()).isFalse();
+ assertThat(savedPreferences.getMarketingEmails()).isFalse();
+ }
+
+ @Test
+ void existsById_WhenPreferencesExist_ShouldReturnTrue() {
+ // Given
+ UserPreferences savedPreferences = entityManager.persistAndFlush(testPreferences);
+
+ // When
+ boolean exists = userPreferencesRepository.existsById(savedPreferences.getId());
+
+ // Then
+ assertThat(exists).isTrue();
+ }
+
+ @Test
+ void existsById_WhenPreferencesDoNotExist_ShouldReturnFalse() {
+ // When
+ boolean exists = userPreferencesRepository.existsById("non-existent-id");
+
+ // Then
+ assertThat(exists).isFalse();
+ }
+
+ @Test
+ void deleteById_WhenPreferencesExist_ShouldRemovePreferences() {
+ // Given
+ UserPreferences savedPreferences = entityManager.persistAndFlush(testPreferences);
+ String preferencesId = savedPreferences.getId();
+
+ // When
+ userPreferencesRepository.deleteById(preferencesId);
+ entityManager.flush();
+
+ // Then
+ Optional result = userPreferencesRepository.findById(preferencesId);
+ assertThat(result).isEmpty();
+ }
+}
\ No newline at end of file
diff --git a/auth-service/src/test/java/com/techtorque/auth_service/repository/UserRepositoryTest.java b/auth-service/src/test/java/com/techtorque/auth_service/repository/UserRepositoryTest.java
new file mode 100644
index 0000000..208abbf
--- /dev/null
+++ b/auth-service/src/test/java/com/techtorque/auth_service/repository/UserRepositoryTest.java
@@ -0,0 +1,382 @@
+package com.techtorque.auth_service.repository;
+
+import com.techtorque.auth_service.entity.Role;
+import com.techtorque.auth_service.entity.RoleName;
+import com.techtorque.auth_service.entity.User;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
+import org.springframework.test.context.ActiveProfiles;
+
+import java.time.LocalDateTime;
+import java.util.Optional;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Comprehensive test class for UserRepository
+ * Tests all repository methods, edge cases, and database constraints
+ */
+@DataJpaTest
+@ActiveProfiles("test")
+class UserRepositoryTest {
+
+ @Autowired
+ private TestEntityManager entityManager;
+
+ @Autowired
+ private UserRepository userRepository;
+
+ @Autowired
+ private RoleRepository roleRepository;
+
+ private User testUser;
+ private Role testRole;
+
+ @BeforeEach
+ void setUp() {
+ // Create test role
+ testRole = Role.builder()
+ .name(RoleName.CUSTOMER)
+ .description("Test customer role")
+ .build();
+ testRole = entityManager.persistAndFlush(testRole);
+
+ // Create test user
+ testUser = User.builder()
+ .username("testuser")
+ .password("encodedPassword")
+ .email("test@example.com")
+ .fullName("Test User")
+ .phone("1234567890")
+ .address("123 Test Street")
+ .enabled(true)
+ .emailVerified(false)
+ .createdAt(LocalDateTime.now())
+ .build();
+ testUser.addRole(testRole);
+ }
+
+ @Test
+ void findByUsername_WhenUserExists_ShouldReturnUser() {
+ // Given
+ entityManager.persistAndFlush(testUser);
+
+ // When
+ Optional result = userRepository.findByUsername("testuser");
+
+ // Then
+ assertThat(result).isPresent();
+ assertThat(result.get().getUsername()).isEqualTo("testuser");
+ assertThat(result.get().getEmail()).isEqualTo("test@example.com");
+ assertThat(result.get().getFullName()).isEqualTo("Test User");
+ }
+
+ @Test
+ void findByUsername_WhenUserDoesNotExist_ShouldReturnEmpty() {
+ // When
+ Optional result = userRepository.findByUsername("nonexistent");
+
+ // Then
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ void findByUsername_CaseSensitive_ShouldReturnEmpty() {
+ // Given
+ entityManager.persistAndFlush(testUser);
+
+ // When
+ Optional result = userRepository.findByUsername("TESTUSER");
+
+ // Then
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ void findByEmail_WhenEmailExists_ShouldReturnUser() {
+ // Given
+ entityManager.persistAndFlush(testUser);
+
+ // When
+ Optional result = userRepository.findByEmail("test@example.com");
+
+ // Then
+ assertThat(result).isPresent();
+ assertThat(result.get().getEmail()).isEqualTo("test@example.com");
+ assertThat(result.get().getUsername()).isEqualTo("testuser");
+ }
+
+ @Test
+ void findByEmail_WhenEmailDoesNotExist_ShouldReturnEmpty() {
+ // When
+ Optional result = userRepository.findByEmail("nonexistent@example.com");
+
+ // Then
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ void findByEmail_CaseSensitive_ShouldReturnEmpty() {
+ // Given
+ entityManager.persistAndFlush(testUser);
+
+ // When
+ Optional result = userRepository.findByEmail("TEST@EXAMPLE.COM");
+
+ // Then
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ void existsByUsername_WhenUsernameExists_ShouldReturnTrue() {
+ // Given
+ entityManager.persistAndFlush(testUser);
+
+ // When
+ boolean exists = userRepository.existsByUsername("testuser");
+
+ // Then
+ assertThat(exists).isTrue();
+ }
+
+ @Test
+ void existsByUsername_WhenUsernameDoesNotExist_ShouldReturnFalse() {
+ // When
+ boolean exists = userRepository.existsByUsername("nonexistent");
+
+ // Then
+ assertThat(exists).isFalse();
+ }
+
+ @Test
+ void existsByEmail_WhenEmailExists_ShouldReturnTrue() {
+ // Given
+ entityManager.persistAndFlush(testUser);
+
+ // When
+ boolean exists = userRepository.existsByEmail("test@example.com");
+
+ // Then
+ assertThat(exists).isTrue();
+ }
+
+ @Test
+ void existsByEmail_WhenEmailDoesNotExist_ShouldReturnFalse() {
+ // When
+ boolean exists = userRepository.existsByEmail("nonexistent@example.com");
+
+ // Then
+ assertThat(exists).isFalse();
+ }
+
+ @Test
+ void findByUsernameWithRoles_WhenUserExists_ShouldReturnUserWithRoles() {
+ // Given
+ entityManager.persistAndFlush(testUser);
+
+ // When
+ Optional result = userRepository.findByUsernameWithRoles("testuser");
+
+ // Then
+ assertThat(result).isPresent();
+ assertThat(result.get().getUsername()).isEqualTo("testuser");
+ assertThat(result.get().getRoles()).isNotEmpty();
+ assertThat(result.get().getRoles()).hasSize(1);
+ assertThat(result.get().getRoles().iterator().next().getName()).isEqualTo(RoleName.CUSTOMER);
+ }
+
+ @Test
+ void findByUsernameWithRoles_WhenUserDoesNotExist_ShouldReturnEmpty() {
+ // When
+ Optional result = userRepository.findByUsernameWithRoles("nonexistent");
+
+ // Then
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ void save_WhenValidUser_ShouldPersistSuccessfully() {
+ // Given
+ User newUser = User.builder()
+ .username("newuser")
+ .password("password")
+ .email("newuser@example.com")
+ .fullName("New User")
+ .enabled(true)
+ .emailVerified(false)
+ .createdAt(LocalDateTime.now())
+ .build();
+
+ // When
+ User savedUser = userRepository.save(newUser);
+
+ // Then
+ assertThat(savedUser.getId()).isNotNull();
+ assertThat(savedUser.getUsername()).isEqualTo("newuser");
+ assertThat(savedUser.getEmail()).isEqualTo("newuser@example.com");
+ assertThat(savedUser.getEnabled()).isTrue();
+ assertThat(savedUser.getEmailVerified()).isFalse();
+ }
+
+ @Test
+ void save_WithProfilePhoto_ShouldPersistPhotoData() {
+ // Given
+ byte[] photoData = "fake-image-data".getBytes();
+ User userWithPhoto = User.builder()
+ .username("photouser")
+ .password("password")
+ .email("photo@example.com")
+ .profilePhoto(photoData)
+ .profilePhotoMimeType("image/jpeg")
+ .profilePhotoUpdatedAt(LocalDateTime.now())
+ .enabled(true)
+ .emailVerified(false)
+ .createdAt(LocalDateTime.now())
+ .build();
+
+ // When
+ User savedUser = userRepository.save(userWithPhoto);
+
+ // Then
+ assertThat(savedUser.getId()).isNotNull();
+ assertThat(savedUser.getProfilePhoto()).isEqualTo(photoData);
+ assertThat(savedUser.getProfilePhotoMimeType()).isEqualTo("image/jpeg");
+ assertThat(savedUser.getProfilePhotoUpdatedAt()).isNotNull();
+ }
+
+ @Test
+ void findById_WhenUserExists_ShouldReturnUser() {
+ // Given
+ User savedUser = entityManager.persistAndFlush(testUser);
+
+ // When
+ Optional result = userRepository.findById(savedUser.getId());
+
+ // Then
+ assertThat(result).isPresent();
+ assertThat(result.get().getId()).isEqualTo(savedUser.getId());
+ assertThat(result.get().getUsername()).isEqualTo("testuser");
+ }
+
+ @Test
+ void findById_WhenUserDoesNotExist_ShouldReturnEmpty() {
+ // When
+ Optional result = userRepository.findById(999L);
+
+ // Then
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ void deleteById_WhenUserExists_ShouldRemoveUser() {
+ // Given
+ User savedUser = entityManager.persistAndFlush(testUser);
+ Long userId = savedUser.getId();
+
+ // When
+ userRepository.deleteById(userId);
+ entityManager.flush();
+
+ // Then
+ Optional result = userRepository.findById(userId);
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ void findAll_ShouldReturnAllUsers() {
+ // Given
+ User user1 = User.builder()
+ .username("user1")
+ .password("password1")
+ .email("user1@example.com")
+ .enabled(true)
+ .emailVerified(false)
+ .createdAt(LocalDateTime.now())
+ .build();
+
+ User user2 = User.builder()
+ .username("user2")
+ .password("password2")
+ .email("user2@example.com")
+ .enabled(true)
+ .emailVerified(false)
+ .createdAt(LocalDateTime.now())
+ .build();
+
+ entityManager.persistAndFlush(user1);
+ entityManager.persistAndFlush(user2);
+
+ // When
+ var allUsers = userRepository.findAll();
+
+ // Then
+ assertThat(allUsers).hasSize(2);
+ assertThat(allUsers).extracting("username")
+ .containsExactlyInAnyOrder("user1", "user2");
+ }
+
+ @Test
+ void count_ShouldReturnCorrectCount() {
+ // Given
+ entityManager.persistAndFlush(testUser);
+
+ User secondUser = User.builder()
+ .username("user2")
+ .password("password2")
+ .email("user2@example.com")
+ .enabled(true)
+ .emailVerified(false)
+ .createdAt(LocalDateTime.now())
+ .build();
+ entityManager.persistAndFlush(secondUser);
+
+ // When
+ long count = userRepository.count();
+
+ // Then
+ assertThat(count).isEqualTo(2);
+ }
+
+ @Test
+ void update_WhenUserExists_ShouldUpdateSuccessfully() {
+ // Given
+ User savedUser = entityManager.persistAndFlush(testUser);
+ entityManager.detach(savedUser);
+
+ // When
+ savedUser.setFullName("Updated Name");
+ savedUser.setPhone("9876543210");
+ savedUser.setEmailVerified(true);
+ User updatedUser = userRepository.save(savedUser);
+
+ // Then
+ assertThat(updatedUser.getFullName()).isEqualTo("Updated Name");
+ assertThat(updatedUser.getPhone()).isEqualTo("9876543210");
+ assertThat(updatedUser.getEmailVerified()).isTrue();
+ assertThat(updatedUser.getUsername()).isEqualTo("testuser"); // Should remain unchanged
+ }
+
+ @Test
+ void existsById_WhenUserExists_ShouldReturnTrue() {
+ // Given
+ User savedUser = entityManager.persistAndFlush(testUser);
+
+ // When
+ boolean exists = userRepository.existsById(savedUser.getId());
+
+ // Then
+ assertThat(exists).isTrue();
+ }
+
+ @Test
+ void existsById_WhenUserDoesNotExist_ShouldReturnFalse() {
+ // When
+ boolean exists = userRepository.existsById(999L);
+
+ // Then
+ assertThat(exists).isFalse();
+ }
+}
\ No newline at end of file
diff --git a/auth-service/src/test/java/com/techtorque/auth_service/repository/VerificationTokenRepositoryTest.java b/auth-service/src/test/java/com/techtorque/auth_service/repository/VerificationTokenRepositoryTest.java
new file mode 100644
index 0000000..d4c6043
--- /dev/null
+++ b/auth-service/src/test/java/com/techtorque/auth_service/repository/VerificationTokenRepositoryTest.java
@@ -0,0 +1,388 @@
+package com.techtorque.auth_service.repository;
+
+import com.techtorque.auth_service.entity.Role;
+import com.techtorque.auth_service.entity.RoleName;
+import com.techtorque.auth_service.entity.User;
+import com.techtorque.auth_service.entity.VerificationToken;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.Optional;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Comprehensive test class for VerificationTokenRepository
+ * Tests all repository methods, edge cases, and database constraints
+ */
+@DataJpaTest
+@ActiveProfiles("test")
+class VerificationTokenRepositoryTest {
+
+ @Autowired
+ private TestEntityManager entityManager;
+
+ @Autowired
+ private VerificationTokenRepository verificationTokenRepository;
+
+ private User testUser;
+ private VerificationToken testToken;
+
+ @BeforeEach
+ void setUp() {
+ // Create test role
+ Role testRole = Role.builder()
+ .name(RoleName.CUSTOMER)
+ .description("Test customer role")
+ .build();
+ testRole = entityManager.persistAndFlush(testRole);
+
+ // Create test user
+ testUser = User.builder()
+ .username("testuser")
+ .password("encodedPassword")
+ .email("test@example.com")
+ .fullName("Test User")
+ .enabled(true)
+ .emailVerified(false)
+ .createdAt(LocalDateTime.now())
+ .build();
+ testUser.addRole(testRole);
+ testUser = entityManager.persistAndFlush(testUser);
+
+ // Create test verification token
+ testToken = VerificationToken.builder()
+ .token("test-verification-token")
+ .user(testUser)
+ .expiryDate(LocalDateTime.now().plusDays(1))
+ .createdAt(LocalDateTime.now())
+ .tokenType(VerificationToken.TokenType.EMAIL_VERIFICATION)
+ .build();
+ }
+
+ @Test
+ void save_WhenValidToken_ShouldPersistSuccessfully() {
+ // When
+ VerificationToken savedToken = verificationTokenRepository.save(testToken);
+
+ // Then
+ assertThat(savedToken.getId()).isNotNull();
+ assertThat(savedToken.getToken()).isEqualTo("test-verification-token");
+ assertThat(savedToken.getUser().getId()).isEqualTo(testUser.getId());
+ assertThat(savedToken.getExpiryDate()).isNotNull();
+ assertThat(savedToken.getCreatedAt()).isNotNull();
+ assertThat(savedToken.getTokenType()).isEqualTo(VerificationToken.TokenType.EMAIL_VERIFICATION);
+ assertThat(savedToken.getUsedAt()).isNull();
+ }
+
+ @Test
+ void findByToken_WhenTokenExists_ShouldReturnToken() {
+ // Given
+ entityManager.persistAndFlush(testToken);
+
+ // When
+ Optional result = verificationTokenRepository.findByToken("test-verification-token");
+
+ // Then
+ assertThat(result).isPresent();
+ assertThat(result.get().getToken()).isEqualTo("test-verification-token");
+ assertThat(result.get().getUser().getId()).isEqualTo(testUser.getId());
+ assertThat(result.get().getTokenType()).isEqualTo(VerificationToken.TokenType.EMAIL_VERIFICATION);
+ }
+
+ @Test
+ void findByToken_WhenTokenDoesNotExist_ShouldReturnEmpty() {
+ // When
+ Optional result = verificationTokenRepository.findByToken("non-existent-token");
+
+ // Then
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ void findByUserAndTokenType_WhenTokenExists_ShouldReturnToken() {
+ // Given
+ entityManager.persistAndFlush(testToken);
+
+ // When
+ Optional result = verificationTokenRepository
+ .findByUserAndTokenType(testUser, VerificationToken.TokenType.EMAIL_VERIFICATION);
+
+ // Then
+ assertThat(result).isPresent();
+ assertThat(result.get().getUser().getId()).isEqualTo(testUser.getId());
+ assertThat(result.get().getTokenType()).isEqualTo(VerificationToken.TokenType.EMAIL_VERIFICATION);
+ }
+
+ @Test
+ void findByUserAndTokenType_WhenTokenDoesNotExist_ShouldReturnEmpty() {
+ // When
+ Optional result = verificationTokenRepository
+ .findByUserAndTokenType(testUser, VerificationToken.TokenType.PASSWORD_RESET);
+
+ // Then
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ void findByUserAndTokenType_WhenDifferentTokenType_ShouldReturnEmpty() {
+ // Given
+ entityManager.persistAndFlush(testToken);
+
+ // When
+ Optional result = verificationTokenRepository
+ .findByUserAndTokenType(testUser, VerificationToken.TokenType.PASSWORD_RESET);
+
+ // Then
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ @Transactional
+ void deleteByUser_WhenUserHasTokens_ShouldDeleteAllUserTokens() {
+ // Given
+ VerificationToken emailToken = VerificationToken.builder()
+ .token("email-token")
+ .user(testUser)
+ .expiryDate(LocalDateTime.now().plusDays(1))
+ .createdAt(LocalDateTime.now())
+ .tokenType(VerificationToken.TokenType.EMAIL_VERIFICATION)
+ .build();
+
+ VerificationToken passwordToken = VerificationToken.builder()
+ .token("password-token")
+ .user(testUser)
+ .expiryDate(LocalDateTime.now().plusHours(1))
+ .createdAt(LocalDateTime.now())
+ .tokenType(VerificationToken.TokenType.PASSWORD_RESET)
+ .build();
+
+ entityManager.persistAndFlush(emailToken);
+ entityManager.persistAndFlush(passwordToken);
+
+ // Create another user with a token to ensure we only delete current user's
+ // tokens
+ User anotherUser = User.builder()
+ .username("anotheruser")
+ .password("password")
+ .email("another@example.com")
+ .enabled(true)
+ .emailVerified(false)
+ .createdAt(LocalDateTime.now())
+ .build();
+ anotherUser = entityManager.persistAndFlush(anotherUser);
+
+ VerificationToken anotherUserToken = VerificationToken.builder()
+ .token("another-token")
+ .user(anotherUser)
+ .expiryDate(LocalDateTime.now().plusDays(1))
+ .createdAt(LocalDateTime.now())
+ .tokenType(VerificationToken.TokenType.EMAIL_VERIFICATION)
+ .build();
+ entityManager.persistAndFlush(anotherUserToken);
+
+ // When
+ verificationTokenRepository.deleteByUser(testUser);
+ entityManager.flush();
+
+ // Then
+ assertThat(verificationTokenRepository.findByToken("email-token")).isEmpty();
+ assertThat(verificationTokenRepository.findByToken("password-token")).isEmpty();
+ assertThat(verificationTokenRepository.findByToken("another-token")).isPresent();
+ }
+
+ @Test
+ void isExpired_WhenTokenIsExpired_ShouldReturnTrue() {
+ // Given
+ VerificationToken expiredToken = VerificationToken.builder()
+ .token("expired-token")
+ .user(testUser)
+ .expiryDate(LocalDateTime.now().minusDays(1))
+ .createdAt(LocalDateTime.now().minusDays(2))
+ .tokenType(VerificationToken.TokenType.EMAIL_VERIFICATION)
+ .build();
+
+ // When
+ boolean isExpired = expiredToken.isExpired();
+
+ // Then
+ assertThat(isExpired).isTrue();
+ }
+
+ @Test
+ void isExpired_WhenTokenIsNotExpired_ShouldReturnFalse() {
+ // Given
+ VerificationToken validToken = VerificationToken.builder()
+ .token("valid-token")
+ .user(testUser)
+ .expiryDate(LocalDateTime.now().plusDays(1))
+ .createdAt(LocalDateTime.now())
+ .tokenType(VerificationToken.TokenType.EMAIL_VERIFICATION)
+ .build();
+
+ // When
+ boolean isExpired = validToken.isExpired();
+
+ // Then
+ assertThat(isExpired).isFalse();
+ }
+
+ @Test
+ void isUsed_WhenTokenIsUsed_ShouldReturnTrue() {
+ // Given
+ VerificationToken usedToken = VerificationToken.builder()
+ .token("used-token")
+ .user(testUser)
+ .expiryDate(LocalDateTime.now().plusDays(1))
+ .createdAt(LocalDateTime.now())
+ .usedAt(LocalDateTime.now())
+ .tokenType(VerificationToken.TokenType.EMAIL_VERIFICATION)
+ .build();
+
+ // When
+ boolean isUsed = usedToken.isUsed();
+
+ // Then
+ assertThat(isUsed).isTrue();
+ }
+
+ @Test
+ void isUsed_WhenTokenIsNotUsed_ShouldReturnFalse() {
+ // Given
+ VerificationToken unusedToken = VerificationToken.builder()
+ .token("unused-token")
+ .user(testUser)
+ .expiryDate(LocalDateTime.now().plusDays(1))
+ .createdAt(LocalDateTime.now())
+ .usedAt(null)
+ .tokenType(VerificationToken.TokenType.EMAIL_VERIFICATION)
+ .build();
+
+ // When
+ boolean isUsed = unusedToken.isUsed();
+
+ // Then
+ assertThat(isUsed).isFalse();
+ }
+
+ @Test
+ void save_WithPasswordResetType_ShouldPersistSuccessfully() {
+ // Given
+ VerificationToken passwordResetToken = VerificationToken.builder()
+ .token("password-reset-token")
+ .user(testUser)
+ .expiryDate(LocalDateTime.now().plusHours(1))
+ .createdAt(LocalDateTime.now())
+ .tokenType(VerificationToken.TokenType.PASSWORD_RESET)
+ .build();
+
+ // When
+ VerificationToken savedToken = verificationTokenRepository.save(passwordResetToken);
+
+ // Then
+ assertThat(savedToken.getId()).isNotNull();
+ assertThat(savedToken.getToken()).isEqualTo("password-reset-token");
+ assertThat(savedToken.getTokenType()).isEqualTo(VerificationToken.TokenType.PASSWORD_RESET);
+ }
+
+ @Test
+ void findById_WhenTokenExists_ShouldReturnToken() {
+ // Given
+ VerificationToken savedToken = entityManager.persistAndFlush(testToken);
+
+ // When
+ Optional result = verificationTokenRepository.findById(savedToken.getId());
+
+ // Then
+ assertThat(result).isPresent();
+ assertThat(result.get().getId()).isEqualTo(savedToken.getId());
+ assertThat(result.get().getToken()).isEqualTo("test-verification-token");
+ }
+
+ @Test
+ void findById_WhenTokenDoesNotExist_ShouldReturnEmpty() {
+ // When
+ Optional result = verificationTokenRepository.findById("non-existent-id");
+
+ // Then
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ void update_WhenTokenIsUsed_ShouldUpdateUsedAt() {
+ // Given
+ VerificationToken savedToken = entityManager.persistAndFlush(testToken);
+ entityManager.detach(savedToken);
+
+ // When
+ LocalDateTime usedTime = LocalDateTime.now();
+ savedToken.setUsedAt(usedTime);
+ VerificationToken updatedToken = verificationTokenRepository.save(savedToken);
+
+ // Then
+ assertThat(updatedToken.getUsedAt()).isEqualTo(usedTime);
+ assertThat(updatedToken.isUsed()).isTrue();
+ }
+
+ @Test
+ void findAll_ShouldReturnAllTokens() {
+ // Given
+ VerificationToken emailToken = VerificationToken.builder()
+ .token("email-token")
+ .user(testUser)
+ .expiryDate(LocalDateTime.now().plusDays(1))
+ .createdAt(LocalDateTime.now())
+ .tokenType(VerificationToken.TokenType.EMAIL_VERIFICATION)
+ .build();
+
+ VerificationToken passwordToken = VerificationToken.builder()
+ .token("password-token")
+ .user(testUser)
+ .expiryDate(LocalDateTime.now().plusHours(1))
+ .createdAt(LocalDateTime.now())
+ .tokenType(VerificationToken.TokenType.PASSWORD_RESET)
+ .build();
+
+ entityManager.persistAndFlush(emailToken);
+ entityManager.persistAndFlush(passwordToken);
+
+ // When
+ var allTokens = verificationTokenRepository.findAll();
+
+ // Then
+ assertThat(allTokens).hasSize(2);
+ assertThat(allTokens).extracting("token")
+ .containsExactlyInAnyOrder("email-token", "password-token");
+ assertThat(allTokens).extracting("tokenType")
+ .containsExactlyInAnyOrder(
+ VerificationToken.TokenType.EMAIL_VERIFICATION,
+ VerificationToken.TokenType.PASSWORD_RESET);
+ }
+
+ @Test
+ void count_ShouldReturnCorrectCount() {
+ // Given
+ entityManager.persistAndFlush(testToken);
+
+ VerificationToken secondToken = VerificationToken.builder()
+ .token("second-token")
+ .user(testUser)
+ .expiryDate(LocalDateTime.now().plusDays(1))
+ .createdAt(LocalDateTime.now())
+ .tokenType(VerificationToken.TokenType.PASSWORD_RESET)
+ .build();
+ entityManager.persistAndFlush(secondToken);
+
+ // When
+ long count = verificationTokenRepository.count();
+
+ // Then
+ assertThat(count).isEqualTo(2);
+ }
+}
\ No newline at end of file
diff --git a/auth-service/src/test/java/com/techtorque/auth_service/service/AuthServiceTest.java b/auth-service/src/test/java/com/techtorque/auth_service/service/AuthServiceTest.java
new file mode 100644
index 0000000..e3d2e02
--- /dev/null
+++ b/auth-service/src/test/java/com/techtorque/auth_service/service/AuthServiceTest.java
@@ -0,0 +1,596 @@
+package com.techtorque.auth_service.service;
+
+import com.techtorque.auth_service.dto.request.LoginRequest;
+import com.techtorque.auth_service.dto.request.RegisterRequest;
+import com.techtorque.auth_service.dto.response.LoginResponse;
+import com.techtorque.auth_service.entity.*;
+import com.techtorque.auth_service.repository.*;
+import com.techtorque.auth_service.util.JwtUtil;
+import jakarta.servlet.http.HttpServletRequest;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.DisabledException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.time.LocalDateTime;
+import java.util.*;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * Comprehensive test class for AuthService
+ * Tests authentication, registration, token management, and security features
+ */
+@ExtendWith(MockitoExtension.class)
+class AuthServiceTest {
+
+ @Mock
+ private AuthenticationManager authenticationManager;
+
+ @Mock
+ private UserRepository userRepository;
+
+ @Mock
+ private RoleRepository roleRepository;
+
+ @Mock
+ private PasswordEncoder passwordEncoder;
+
+ @Mock
+ private JwtUtil jwtUtil;
+
+ @Mock
+ private LoginLockRepository loginLockRepository;
+
+ @Mock
+ private LoginLogRepository loginLogRepository;
+
+ @Mock
+ private LoginAuditService loginAuditService;
+
+ @Mock
+ private TokenService tokenService;
+
+ @Mock
+ private EmailService emailService;
+
+ @Mock
+ private HttpServletRequest request;
+
+ @Mock
+ private Authentication authentication;
+
+ @Mock
+ private UserDetails userDetails;
+
+ @InjectMocks
+ private AuthService authService;
+
+ private User testUser;
+ private Role customerRole;
+ private Role adminRole;
+ private LoginRequest loginRequest;
+ private RegisterRequest registerRequest;
+ private LoginLock loginLock;
+ private VerificationToken verificationToken;
+ private RefreshToken refreshToken;
+
+ @BeforeEach
+ void setUp() {
+ // Set up test configuration
+ ReflectionTestUtils.setField(authService, "maxFailedAttempts", 3);
+ ReflectionTestUtils.setField(authService, "lockDurationMinutes", 15L);
+
+ // Mock request IP address (lenient to avoid unnecessary stubbing warnings)
+ lenient().when(request.getRemoteAddr()).thenReturn("192.168.1.1");
+ lenient().when(request.getHeader("X-Forwarded-For")).thenReturn(null);
+
+ // Create test roles
+ customerRole = Role.builder()
+ .id(1L)
+ .name(RoleName.CUSTOMER)
+ .description("Customer role")
+ .permissions(Collections.emptySet())
+ .build();
+
+ adminRole = Role.builder()
+ .id(2L)
+ .name(RoleName.ADMIN)
+ .description("Admin role")
+ .permissions(Collections.emptySet())
+ .build();
+
+ // Create test user
+ testUser = User.builder()
+ .id(1L)
+ .username("testuser")
+ .email("test@example.com")
+ .password("encoded-password")
+ .fullName("Test User")
+ .phone("1234567890")
+ .address("Test Address")
+ .enabled(true)
+ .emailVerified(true)
+ .createdAt(LocalDateTime.now())
+ .roles(Set.of(customerRole))
+ .build();
+
+ // Create test requests
+ loginRequest = new LoginRequest();
+ loginRequest.setUsername("testuser");
+ loginRequest.setPassword("password");
+
+ registerRequest = new RegisterRequest();
+ registerRequest.setEmail("new@example.com");
+ registerRequest.setPassword("newpassword");
+ registerRequest.setFullName("New User");
+ registerRequest.setPhone("9876543210");
+ registerRequest.setAddress("New Address");
+
+ // Create test login lock
+ loginLock = LoginLock.builder()
+ .id(1L)
+ .username("testuser")
+ .failedAttempts(0)
+ .lockUntil(null)
+ .build();
+
+ // Create test verification token
+ verificationToken = VerificationToken.builder()
+ .id("token-1")
+ .token("verification-token")
+ .user(testUser)
+ .tokenType(VerificationToken.TokenType.EMAIL_VERIFICATION)
+ .expiryDate(LocalDateTime.now().plusHours(24))
+ .createdAt(LocalDateTime.now())
+ .build();
+
+ // Create test refresh token
+ refreshToken = RefreshToken.builder()
+ .id("refresh-1")
+ .token("refresh-token")
+ .user(testUser)
+ .expiryDate(LocalDateTime.now().plusDays(7))
+ .createdAt(LocalDateTime.now())
+ .build();
+ }
+
+ @Test
+ void authenticateUser_WhenValidCredentials_ShouldReturnLoginResponse() {
+ // Given
+ when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(testUser));
+ when(loginLockRepository.findByUsername("testuser")).thenReturn(Optional.of(loginLock));
+ when(authenticationManager.authenticate(any(UsernamePasswordAuthenticationToken.class)))
+ .thenReturn(authentication);
+ when(authentication.getPrincipal()).thenReturn(userDetails);
+ when(userDetails.getUsername()).thenReturn("testuser");
+ when(userDetails.getAuthorities())
+ .thenReturn((Collection) java.util.Collections.singletonList(
+ new SimpleGrantedAuthority("ROLE_CUSTOMER")));
+ when(jwtUtil.generateJwtToken(eq(userDetails), anyList())).thenReturn("jwt-token");
+ when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(testUser));
+ when(tokenService.createRefreshToken(any(User.class), anyString(), anyString()))
+ .thenReturn("refresh-token");
+ when(request.getHeader("X-Forwarded-For")).thenReturn(null);
+ when(request.getRemoteAddr()).thenReturn("127.0.0.1");
+ when(request.getHeader("User-Agent")).thenReturn("Test-Agent");
+
+ // When
+ LoginResponse response = authService.authenticateUser(loginRequest, request);
+
+ // Then
+ assertThat(response).isNotNull();
+ assertThat(response.getToken()).isEqualTo("jwt-token");
+ assertThat(response.getRefreshToken()).isEqualTo("refresh-token");
+ assertThat(response.getUsername()).isEqualTo("testuser");
+ assertThat(response.getEmail()).isEqualTo("test@example.com");
+ assertThat(response.getRoles()).contains("CUSTOMER");
+
+ verify(loginLockRepository).save(any(LoginLock.class));
+ verify(loginLogRepository).save(any(LoginLog.class));
+ }
+
+ @Test
+ void authenticateUser_WhenUserNotFound_ShouldCheckByEmail() {
+ // Given
+ when(userRepository.findByUsername("test@example.com")).thenReturn(Optional.empty());
+ lenient().when(userRepository.findByEmail("test@example.com")).thenReturn(Optional.of(testUser));
+ when(loginLockRepository.findByUsername("test@example.com")).thenReturn(Optional.of(loginLock));
+ when(authenticationManager.authenticate(any(UsernamePasswordAuthenticationToken.class)))
+ .thenReturn(authentication);
+ when(authentication.getPrincipal()).thenReturn(userDetails);
+ when(userDetails.getUsername()).thenReturn("test@example.com");
+ when(userDetails.getAuthorities()).thenReturn((Collection) java.util.Collections
+ .singletonList(new SimpleGrantedAuthority("ROLE_CUSTOMER")));
+
+ loginRequest.setUsername("test@example.com");
+
+ // When/Then
+ when(jwtUtil.generateJwtToken(eq(userDetails), anyList())).thenReturn("jwt-token");
+ when(userRepository.findByUsername("test@example.com")).thenReturn(Optional.of(testUser));
+ when(tokenService.createRefreshToken(any(User.class), eq("192.168.1.1"), isNull()))
+ .thenReturn("refresh-token");
+
+ LoginResponse response = authService.authenticateUser(loginRequest, request);
+ assertThat(response).isNotNull();
+ }
+
+ @Test
+ void authenticateUser_WhenUserDisabledAndNotVerified_ShouldThrowDisabledException() {
+ // Given
+ testUser.setEnabled(false);
+ testUser.setEmailVerified(false);
+ when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(testUser));
+
+ // When/Then
+ assertThatThrownBy(() -> authService.authenticateUser(loginRequest, request))
+ .isInstanceOf(DisabledException.class)
+ .hasMessageContaining("Please verify your email address");
+ }
+
+ @Test
+ void authenticateUser_WhenUserDisabledAndVerified_ShouldThrowDisabledException() {
+ // Given
+ testUser.setEnabled(false);
+ testUser.setEmailVerified(true);
+ when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(testUser));
+
+ // When/Then
+ assertThatThrownBy(() -> authService.authenticateUser(loginRequest, request))
+ .isInstanceOf(DisabledException.class)
+ .hasMessageContaining("Your account has been deactivated");
+ }
+
+ @Test
+ void authenticateUser_WhenAccountLocked_ShouldThrowBadCredentialsException() {
+ // Given
+ loginLock.setLockUntil(LocalDateTime.now().plusMinutes(10));
+ when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(testUser));
+ when(loginLockRepository.findByUsername("testuser")).thenReturn(Optional.of(loginLock));
+
+ // When/Then
+ assertThatThrownBy(() -> authService.authenticateUser(loginRequest, request))
+ .isInstanceOf(BadCredentialsException.class)
+ .hasMessageContaining("Account is temporarily locked");
+
+ verify(loginAuditService).recordLogin(eq("testuser"), eq(false), eq("192.168.1.1"), isNull());
+ }
+
+ @Test
+ void authenticateUser_WhenNoLockRecord_ShouldCreateNewLock() {
+ // Given
+ when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(testUser));
+ when(loginLockRepository.findByUsername("testuser")).thenReturn(Optional.empty());
+ when(authenticationManager.authenticate(any(UsernamePasswordAuthenticationToken.class)))
+ .thenReturn(authentication);
+ when(authentication.getPrincipal()).thenReturn(userDetails);
+ when(userDetails.getUsername()).thenReturn("testuser");
+ when(userDetails.getAuthorities())
+ .thenReturn((Collection) java.util.Collections
+ .singleton(new SimpleGrantedAuthority("ROLE_CUSTOMER")));
+ when(jwtUtil.generateJwtToken(eq(userDetails), anyList())).thenReturn("jwt-token");
+ when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(testUser));
+ when(tokenService.createRefreshToken(any(User.class), eq("192.168.1.1"), isNull()))
+ .thenReturn("refresh-token");
+
+ // When
+ LoginResponse response = authService.authenticateUser(loginRequest, request);
+
+ // Then
+ assertThat(response).isNotNull();
+ verify(loginLockRepository)
+ .save(argThat(lock -> lock.getUsername().equals("testuser")
+ && lock.getFailedAttempts() == 0));
+ }
+
+ @Test
+ void authenticateUser_WhenBadCredentials_ShouldIncrementFailedAttempts() {
+ // Given
+ when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(testUser));
+ when(loginLockRepository.findByUsername("testuser")).thenReturn(Optional.of(loginLock));
+ when(authenticationManager.authenticate(any(UsernamePasswordAuthenticationToken.class)))
+ .thenThrow(new BadCredentialsException("Invalid credentials"));
+
+ // When/Then
+ assertThatThrownBy(() -> authService.authenticateUser(loginRequest, request))
+ .isInstanceOf(BadCredentialsException.class)
+ .hasMessage("Invalid username or password");
+
+ verify(loginAuditService).incrementFailedAttempt("testuser", 15L, 3);
+ verify(loginAuditService).recordLogin(eq("testuser"), eq(false), eq("192.168.1.1"), isNull());
+ }
+
+ @Test
+ void registerUser_WhenValidRequest_ShouldCreateUserAndSendVerificationEmail() {
+ // Given
+ when(userRepository.existsByEmail("new@example.com")).thenReturn(false);
+ when(roleRepository.findByName(RoleName.CUSTOMER)).thenReturn(Optional.of(customerRole));
+ when(passwordEncoder.encode("newpassword")).thenReturn("encoded-password");
+ when(userRepository.save(any(User.class))).thenReturn(testUser);
+ when(tokenService.createVerificationToken(any(User.class))).thenReturn("verification-token");
+
+ // When
+ String result = authService.registerUser(registerRequest);
+
+ // Then
+ assertThat(result).contains("User registered successfully");
+ verify(userRepository).save(argThat(user -> user.getEmail().equals("new@example.com") &&
+ user.getFullName().equals("New User") &&
+ !user.getEnabled() &&
+ !user.getEmailVerified()));
+ verify(emailService).sendVerificationEmail("test@example.com", "testuser", "verification-token");
+ }
+
+ @Test
+ void registerUser_WhenEmailExists_ShouldThrowRuntimeException() {
+ // Given
+ when(userRepository.existsByEmail("new@example.com")).thenReturn(true);
+
+ // When/Then
+ assertThatThrownBy(() -> authService.registerUser(registerRequest))
+ .isInstanceOf(RuntimeException.class)
+ .hasMessage("Error: Email is already in use!");
+ }
+
+ @Test
+ void registerUser_WhenAdminRole_ShouldAssignAdminRole() {
+ // Given
+ registerRequest.setRoles(Set.of("admin"));
+ when(userRepository.existsByEmail("new@example.com")).thenReturn(false);
+ when(roleRepository.findByName(RoleName.ADMIN)).thenReturn(Optional.of(adminRole));
+ when(passwordEncoder.encode("newpassword")).thenReturn("encoded-password");
+ when(userRepository.save(any(User.class))).thenReturn(testUser);
+ when(tokenService.createVerificationToken(any(User.class))).thenReturn("verification-token");
+
+ // When
+ authService.registerUser(registerRequest);
+
+ // Then
+ verify(roleRepository).findByName(RoleName.ADMIN);
+ verify(userRepository).save(argThat(user -> user.getRoles().contains(adminRole)));
+ }
+
+ @Test
+ void registerUser_WhenEmployeeRole_ShouldAssignEmployeeRole() {
+ // Given
+ Role employeeRole = Role.builder().id(3L).name(RoleName.EMPLOYEE).build();
+ registerRequest.setRoles(Set.of("employee"));
+ when(userRepository.existsByEmail("new@example.com")).thenReturn(false);
+ when(roleRepository.findByName(RoleName.EMPLOYEE)).thenReturn(Optional.of(employeeRole));
+ when(passwordEncoder.encode("newpassword")).thenReturn("encoded-password");
+ when(userRepository.save(any(User.class))).thenReturn(testUser);
+ when(tokenService.createVerificationToken(any(User.class))).thenReturn("verification-token");
+
+ // When
+ authService.registerUser(registerRequest);
+
+ // Then
+ verify(roleRepository).findByName(RoleName.EMPLOYEE);
+ verify(userRepository).save(argThat(user -> user.getRoles().contains(employeeRole)));
+ }
+
+ @Test
+ void registerUser_WhenInvalidRole_ShouldAssignCustomerRole() {
+ // Given
+ registerRequest.setRoles(Set.of("invalid"));
+ when(userRepository.existsByEmail("new@example.com")).thenReturn(false);
+ when(roleRepository.findByName(RoleName.CUSTOMER)).thenReturn(Optional.of(customerRole));
+ when(passwordEncoder.encode("newpassword")).thenReturn("encoded-password");
+ when(userRepository.save(any(User.class))).thenReturn(testUser);
+ when(tokenService.createVerificationToken(any(User.class))).thenReturn("verification-token");
+
+ // When
+ authService.registerUser(registerRequest);
+
+ // Then
+ verify(roleRepository).findByName(RoleName.CUSTOMER);
+ verify(userRepository).save(argThat(user -> user.getRoles().contains(customerRole)));
+ }
+
+ @Test
+ void registerUser_WhenRoleNotFound_ShouldThrowRuntimeException() {
+ // Given
+ when(userRepository.existsByEmail("new@example.com")).thenReturn(false);
+ when(roleRepository.findByName(RoleName.CUSTOMER)).thenReturn(Optional.empty());
+ when(passwordEncoder.encode("newpassword")).thenReturn("encoded-password");
+
+ // When/Then
+ assertThatThrownBy(() -> authService.registerUser(registerRequest))
+ .isInstanceOf(RuntimeException.class)
+ .hasMessage("Error: Customer Role not found.");
+ }
+
+ @Test
+ void verifyEmail_WhenValidToken_ShouldEnableUserAndReturnLoginResponse() {
+ // Given
+ testUser.setEnabled(false);
+ testUser.setEmailVerified(false);
+ when(tokenService.validateToken("verification-token", VerificationToken.TokenType.EMAIL_VERIFICATION))
+ .thenReturn(verificationToken);
+ when(userRepository.save(any(User.class))).thenReturn(testUser);
+ when(jwtUtil.generateJwtToken(any(UserDetails.class), anyList())).thenReturn("jwt-token");
+ when(tokenService.createRefreshToken(any(User.class), eq("192.168.1.1"), isNull()))
+ .thenReturn("refresh-token");
+
+ // When
+ LoginResponse response = authService.verifyEmail("verification-token", request);
+
+ // Then
+ assertThat(response).isNotNull();
+ assertThat(response.getToken()).isEqualTo("jwt-token");
+ assertThat(response.getRefreshToken()).isEqualTo("refresh-token");
+ verify(userRepository).save(argThat(user -> user.getEnabled() && user.getEmailVerified()));
+ verify(tokenService).markTokenAsUsed(verificationToken);
+ verify(emailService).sendWelcomeEmail("test@example.com", "testuser");
+ }
+
+ @Test
+ void resendVerificationEmail_WhenUserExists_ShouldSendEmail() {
+ // Given
+ testUser.setEmailVerified(false);
+ when(userRepository.findByEmail("test@example.com")).thenReturn(Optional.of(testUser));
+ when(tokenService.createVerificationToken(testUser)).thenReturn("new-verification-token");
+
+ // When
+ String result = authService.resendVerificationEmail("test@example.com");
+
+ // Then
+ assertThat(result).contains("Verification email sent successfully");
+ verify(emailService).sendVerificationEmail("test@example.com", "testuser", "new-verification-token");
+ }
+
+ @Test
+ void resendVerificationEmail_WhenUserNotFound_ShouldThrowRuntimeException() {
+ // Given
+ when(userRepository.findByEmail("nonexistent@example.com")).thenReturn(Optional.empty());
+
+ // When/Then
+ assertThatThrownBy(() -> authService.resendVerificationEmail("nonexistent@example.com"))
+ .isInstanceOf(RuntimeException.class)
+ .hasMessage("User not found with email: nonexistent@example.com");
+ }
+
+ @Test
+ void resendVerificationEmail_WhenEmailAlreadyVerified_ShouldThrowRuntimeException() {
+ // Given
+ testUser.setEmailVerified(true);
+ when(userRepository.findByEmail("test@example.com")).thenReturn(Optional.of(testUser));
+
+ // When/Then
+ assertThatThrownBy(() -> authService.resendVerificationEmail("test@example.com"))
+ .isInstanceOf(RuntimeException.class)
+ .hasMessage("Email is already verified");
+ }
+
+ @Test
+ void refreshToken_WhenValidToken_ShouldReturnNewLoginResponse() {
+ // Given
+ when(tokenService.validateRefreshToken("refresh-token")).thenReturn(refreshToken);
+ when(jwtUtil.generateJwtToken(any(UserDetails.class), anyList())).thenReturn("new-jwt-token");
+
+ // When
+ LoginResponse response = authService.refreshToken("refresh-token");
+
+ // Then
+ assertThat(response).isNotNull();
+ assertThat(response.getToken()).isEqualTo("new-jwt-token");
+ assertThat(response.getRefreshToken()).isEqualTo("refresh-token");
+ assertThat(response.getUsername()).isEqualTo("testuser");
+ assertThat(response.getEmail()).isEqualTo("test@example.com");
+ }
+
+ @Test
+ void logout_ShouldRevokeRefreshToken() {
+ // When
+ authService.logout("refresh-token");
+
+ // Then
+ verify(tokenService).revokeRefreshToken("refresh-token");
+ }
+
+ @Test
+ void forgotPassword_WhenUserExists_ShouldSendResetEmail() {
+ // Given
+ when(userRepository.findByEmail("test@example.com")).thenReturn(Optional.of(testUser));
+ when(tokenService.createPasswordResetToken(testUser)).thenReturn("reset-token");
+
+ // When
+ String result = authService.forgotPassword("test@example.com");
+
+ // Then
+ assertThat(result).contains("Password reset email sent successfully");
+ verify(emailService).sendPasswordResetEmail("test@example.com", "testuser", "reset-token");
+ }
+
+ @Test
+ void forgotPassword_WhenUserNotFound_ShouldThrowRuntimeException() {
+ // Given
+ when(userRepository.findByEmail("nonexistent@example.com")).thenReturn(Optional.empty());
+
+ // When/Then
+ assertThatThrownBy(() -> authService.forgotPassword("nonexistent@example.com"))
+ .isInstanceOf(RuntimeException.class)
+ .hasMessage("User not found with email: nonexistent@example.com");
+ }
+
+ @Test
+ void resetPassword_WhenValidToken_ShouldUpdatePasswordAndRevokeTokens() {
+ // Given
+ VerificationToken resetToken = VerificationToken.builder()
+ .token("reset-token")
+ .user(testUser)
+ .tokenType(VerificationToken.TokenType.PASSWORD_RESET)
+ .build();
+
+ when(tokenService.validateToken("reset-token", VerificationToken.TokenType.PASSWORD_RESET))
+ .thenReturn(resetToken);
+ when(passwordEncoder.encode("newpassword")).thenReturn("new-encoded-password");
+ when(userRepository.save(any(User.class))).thenReturn(testUser);
+
+ // When
+ String result = authService.resetPassword("reset-token", "newpassword");
+
+ // Then
+ assertThat(result).contains("Password reset successfully");
+ verify(userRepository).save(argThat(user -> user.getPassword().equals("new-encoded-password")));
+ verify(tokenService).markTokenAsUsed(resetToken);
+ verify(tokenService).revokeAllUserTokens(testUser);
+ }
+
+ @Test
+ void authenticateUser_WhenNullRequest_ShouldHandleGracefully() {
+ // Given
+ when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(testUser));
+ when(loginLockRepository.findByUsername("testuser")).thenReturn(Optional.of(loginLock));
+ when(authenticationManager.authenticate(any(UsernamePasswordAuthenticationToken.class)))
+ .thenReturn(authentication);
+ when(authentication.getPrincipal()).thenReturn(userDetails);
+ when(userDetails.getUsername()).thenReturn("testuser");
+ when(userDetails.getAuthorities())
+ .thenReturn((Collection) java.util.Collections.singletonList(
+ new SimpleGrantedAuthority("ROLE_CUSTOMER")));
+ when(jwtUtil.generateJwtToken(eq(userDetails), anyList())).thenReturn("jwt-token");
+ when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(testUser));
+ when(tokenService.createRefreshToken(any(User.class), isNull(), isNull())).thenReturn("refresh-token");
+
+ // When
+ LoginResponse response = authService.authenticateUser(loginRequest, null);
+
+ // Then
+ assertThat(response).isNotNull();
+ verify(tokenService).createRefreshToken(eq(testUser), isNull(), isNull());
+ }
+
+ @Test
+ void verifyEmail_WhenNullRequest_ShouldHandleGracefully() {
+ // Given
+ testUser.setEnabled(false);
+ testUser.setEmailVerified(false);
+ when(tokenService.validateToken("verification-token", VerificationToken.TokenType.EMAIL_VERIFICATION))
+ .thenReturn(verificationToken);
+ when(userRepository.save(any(User.class))).thenReturn(testUser);
+ when(jwtUtil.generateJwtToken(any(UserDetails.class), anyList())).thenReturn("jwt-token");
+ when(tokenService.createRefreshToken(any(User.class), isNull(), isNull())).thenReturn("refresh-token");
+
+ // When
+ LoginResponse response = authService.verifyEmail("verification-token", null);
+
+ // Then
+ assertThat(response).isNotNull();
+ verify(tokenService).createRefreshToken(eq(testUser), isNull(), isNull());
+ }
+}
diff --git a/auth-service/src/test/java/com/techtorque/auth_service/service/TokenServiceTest.java b/auth-service/src/test/java/com/techtorque/auth_service/service/TokenServiceTest.java
new file mode 100644
index 0000000..49065c8
--- /dev/null
+++ b/auth-service/src/test/java/com/techtorque/auth_service/service/TokenServiceTest.java
@@ -0,0 +1,470 @@
+package com.techtorque.auth_service.service;
+
+import com.techtorque.auth_service.entity.RefreshToken;
+import com.techtorque.auth_service.entity.User;
+import com.techtorque.auth_service.entity.VerificationToken;
+import com.techtorque.auth_service.repository.RefreshTokenRepository;
+import com.techtorque.auth_service.repository.VerificationTokenRepository;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.time.LocalDateTime;
+import java.util.Optional;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * Comprehensive test class for TokenService
+ * Tests token creation, validation, expiry, and cleanup operations
+ */
+@ExtendWith(MockitoExtension.class)
+class TokenServiceTest {
+
+ @Mock
+ private VerificationTokenRepository verificationTokenRepository;
+
+ @Mock
+ private RefreshTokenRepository refreshTokenRepository;
+
+ @InjectMocks
+ private TokenService tokenService;
+
+ private User testUser;
+ private VerificationToken verificationToken;
+ private RefreshToken refreshToken;
+
+ @BeforeEach
+ void setUp() {
+ // Set up test configuration
+ ReflectionTestUtils.setField(tokenService, "verificationExpiryHours", 24);
+ ReflectionTestUtils.setField(tokenService, "passwordResetExpiryHours", 1);
+ ReflectionTestUtils.setField(tokenService, "refreshTokenExpiryDays", 7);
+
+ // Create test user
+ testUser = User.builder()
+ .id(1L)
+ .username("testuser")
+ .email("test@example.com")
+ .password("encoded-password")
+ .enabled(true)
+ .build();
+
+ // Create test verification token
+ verificationToken = VerificationToken.builder()
+ .id("token-1")
+ .token("verification-token")
+ .user(testUser)
+ .tokenType(VerificationToken.TokenType.EMAIL_VERIFICATION)
+ .createdAt(LocalDateTime.now())
+ .expiryDate(LocalDateTime.now().plusHours(24))
+ .build();
+
+ // Create test refresh token
+ refreshToken = RefreshToken.builder()
+ .id("refresh-1")
+ .token("refresh-token")
+ .user(testUser)
+ .createdAt(LocalDateTime.now())
+ .expiryDate(LocalDateTime.now().plusDays(7))
+ .ipAddress("127.0.0.1")
+ .userAgent("Test-Agent")
+ .build();
+ }
+
+ @Test
+ void createVerificationToken_WhenValidUser_ShouldCreateAndReturnToken() {
+ // Given
+ when(verificationTokenRepository.findByUserAndTokenType(testUser,
+ VerificationToken.TokenType.EMAIL_VERIFICATION))
+ .thenReturn(Optional.empty());
+ when(verificationTokenRepository.save(any(VerificationToken.class)))
+ .thenAnswer(invocation -> invocation.getArgument(0));
+
+ // When
+ String result = tokenService.createVerificationToken(testUser);
+
+ // Then
+ assertThat(result).isNotNull();
+ assertThat(result).hasSize(36); // UUID length
+ verify(verificationTokenRepository).save(argThat(token -> token.getUser().equals(testUser) &&
+ token.getTokenType().equals(VerificationToken.TokenType.EMAIL_VERIFICATION) &&
+ token.getExpiryDate().isAfter(LocalDateTime.now().plusHours(23))));
+ }
+
+ @Test
+ void createVerificationToken_WhenExistingTokenExists_ShouldDeleteExistingToken() {
+ // Given
+ VerificationToken existingToken = VerificationToken.builder()
+ .token("existing-token")
+ .user(testUser)
+ .tokenType(VerificationToken.TokenType.EMAIL_VERIFICATION)
+ .build();
+
+ when(verificationTokenRepository.findByUserAndTokenType(testUser,
+ VerificationToken.TokenType.EMAIL_VERIFICATION))
+ .thenReturn(Optional.of(existingToken));
+ when(verificationTokenRepository.save(any(VerificationToken.class)))
+ .thenAnswer(invocation -> invocation.getArgument(0));
+
+ // When
+ String result = tokenService.createVerificationToken(testUser);
+
+ // Then
+ assertThat(result).isNotNull();
+ verify(verificationTokenRepository).delete(existingToken);
+ verify(verificationTokenRepository).save(any(VerificationToken.class));
+ }
+
+ @Test
+ void createPasswordResetToken_WhenValidUser_ShouldCreateAndReturnToken() {
+ // Given
+ when(verificationTokenRepository.findByUserAndTokenType(testUser, VerificationToken.TokenType.PASSWORD_RESET))
+ .thenReturn(Optional.empty());
+ when(verificationTokenRepository.save(any(VerificationToken.class)))
+ .thenAnswer(invocation -> invocation.getArgument(0));
+
+ // When
+ String result = tokenService.createPasswordResetToken(testUser);
+
+ // Then
+ assertThat(result).isNotNull();
+ assertThat(result).hasSize(36); // UUID length
+ verify(verificationTokenRepository).save(argThat(token -> token.getUser().equals(testUser) &&
+ token.getTokenType().equals(VerificationToken.TokenType.PASSWORD_RESET) &&
+ token.getExpiryDate().isAfter(LocalDateTime.now().plusMinutes(30))));
+ }
+
+ @Test
+ void createPasswordResetToken_WhenExistingTokenExists_ShouldDeleteExistingToken() {
+ // Given
+ VerificationToken existingToken = VerificationToken.builder()
+ .token("existing-reset-token")
+ .user(testUser)
+ .tokenType(VerificationToken.TokenType.PASSWORD_RESET)
+ .build();
+
+ when(verificationTokenRepository.findByUserAndTokenType(testUser, VerificationToken.TokenType.PASSWORD_RESET))
+ .thenReturn(Optional.of(existingToken));
+ when(verificationTokenRepository.save(any(VerificationToken.class)))
+ .thenAnswer(invocation -> invocation.getArgument(0));
+
+ // When
+ String result = tokenService.createPasswordResetToken(testUser);
+
+ // Then
+ assertThat(result).isNotNull();
+ verify(verificationTokenRepository).delete(existingToken);
+ verify(verificationTokenRepository).save(any(VerificationToken.class));
+ }
+
+ @Test
+ void validateToken_WhenValidToken_ShouldReturnToken() {
+ // Given
+ when(verificationTokenRepository.findByToken("verification-token")).thenReturn(Optional.of(verificationToken));
+
+ // When
+ VerificationToken result = tokenService.validateToken("verification-token",
+ VerificationToken.TokenType.EMAIL_VERIFICATION);
+
+ // Then
+ assertThat(result).isEqualTo(verificationToken);
+ }
+
+ @Test
+ void validateToken_WhenTokenNotFound_ShouldThrowRuntimeException() {
+ // Given
+ when(verificationTokenRepository.findByToken("invalid-token")).thenReturn(Optional.empty());
+
+ // When/Then
+ assertThatThrownBy(
+ () -> tokenService.validateToken("invalid-token", VerificationToken.TokenType.EMAIL_VERIFICATION))
+ .isInstanceOf(RuntimeException.class)
+ .hasMessage("Invalid token");
+ }
+
+ @Test
+ void validateToken_WhenWrongTokenType_ShouldThrowRuntimeException() {
+ // Given
+ when(verificationTokenRepository.findByToken("verification-token")).thenReturn(Optional.of(verificationToken));
+
+ // When/Then
+ assertThatThrownBy(
+ () -> tokenService.validateToken("verification-token", VerificationToken.TokenType.PASSWORD_RESET))
+ .isInstanceOf(RuntimeException.class)
+ .hasMessage("Invalid token type");
+ }
+
+ @Test
+ void validateToken_WhenTokenAlreadyUsed_ShouldThrowRuntimeException() {
+ // Given
+ verificationToken.setUsedAt(LocalDateTime.now());
+ when(verificationTokenRepository.findByToken("verification-token")).thenReturn(Optional.of(verificationToken));
+
+ // When/Then
+ assertThatThrownBy(
+ () -> tokenService.validateToken("verification-token", VerificationToken.TokenType.EMAIL_VERIFICATION))
+ .isInstanceOf(RuntimeException.class)
+ .hasMessage("Token has already been used");
+ }
+
+ @Test
+ void validateToken_WhenTokenExpired_ShouldThrowRuntimeException() {
+ // Given
+ verificationToken.setExpiryDate(LocalDateTime.now().minusHours(1));
+ when(verificationTokenRepository.findByToken("verification-token")).thenReturn(Optional.of(verificationToken));
+
+ // When/Then
+ assertThatThrownBy(
+ () -> tokenService.validateToken("verification-token", VerificationToken.TokenType.EMAIL_VERIFICATION))
+ .isInstanceOf(RuntimeException.class)
+ .hasMessage("Token has expired");
+ }
+
+ @Test
+ void markTokenAsUsed_ShouldSetUsedAtTimestamp() {
+ // Given
+ when(verificationTokenRepository.save(verificationToken)).thenReturn(verificationToken);
+
+ // When
+ tokenService.markTokenAsUsed(verificationToken);
+
+ // Then
+ assertThat(verificationToken.getUsedAt()).isNotNull();
+ assertThat(verificationToken.getUsedAt()).isBefore(LocalDateTime.now().plusSeconds(1));
+ verify(verificationTokenRepository).save(verificationToken);
+ }
+
+ @Test
+ void createRefreshToken_WhenValidUser_ShouldCreateAndReturnToken() {
+ // Given
+ when(refreshTokenRepository.save(any(RefreshToken.class))).thenAnswer(invocation -> invocation.getArgument(0));
+
+ // When
+ String result = tokenService.createRefreshToken(testUser, "192.168.1.1", "Mozilla/5.0");
+
+ // Then
+ assertThat(result).isNotNull();
+ assertThat(result).hasSize(36); // UUID length
+ verify(refreshTokenRepository).save(argThat(token -> token.getUser().equals(testUser) &&
+ token.getIpAddress().equals("192.168.1.1") &&
+ token.getUserAgent().equals("Mozilla/5.0") &&
+ token.getExpiryDate().isAfter(LocalDateTime.now().plusDays(6))));
+ }
+
+ @Test
+ void createRefreshToken_WhenNullIpAndUserAgent_ShouldCreateTokenWithNulls() {
+ // Given
+ when(refreshTokenRepository.save(any(RefreshToken.class))).thenAnswer(invocation -> invocation.getArgument(0));
+
+ // When
+ String result = tokenService.createRefreshToken(testUser, null, null);
+
+ // Then
+ assertThat(result).isNotNull();
+ verify(refreshTokenRepository).save(argThat(token -> token.getUser().equals(testUser) &&
+ token.getIpAddress() == null &&
+ token.getUserAgent() == null));
+ }
+
+ @Test
+ void validateRefreshToken_WhenValidToken_ShouldReturnToken() {
+ // Given
+ when(refreshTokenRepository.findByToken("refresh-token")).thenReturn(Optional.of(refreshToken));
+
+ // When
+ RefreshToken result = tokenService.validateRefreshToken("refresh-token");
+
+ // Then
+ assertThat(result).isEqualTo(refreshToken);
+ }
+
+ @Test
+ void validateRefreshToken_WhenTokenNotFound_ShouldThrowRuntimeException() {
+ // Given
+ when(refreshTokenRepository.findByToken("invalid-token")).thenReturn(Optional.empty());
+
+ // When/Then
+ assertThatThrownBy(() -> tokenService.validateRefreshToken("invalid-token"))
+ .isInstanceOf(RuntimeException.class)
+ .hasMessage("Invalid refresh token");
+ }
+
+ @Test
+ void validateRefreshToken_WhenTokenRevoked_ShouldThrowRuntimeException() {
+ // Given
+ refreshToken.setRevokedAt(LocalDateTime.now());
+ when(refreshTokenRepository.findByToken("refresh-token")).thenReturn(Optional.of(refreshToken));
+
+ // When/Then
+ assertThatThrownBy(() -> tokenService.validateRefreshToken("refresh-token"))
+ .isInstanceOf(RuntimeException.class)
+ .hasMessage("Refresh token has been revoked");
+ }
+
+ @Test
+ void validateRefreshToken_WhenTokenExpired_ShouldThrowRuntimeException() {
+ // Given
+ refreshToken.setExpiryDate(LocalDateTime.now().minusDays(1));
+ when(refreshTokenRepository.findByToken("refresh-token")).thenReturn(Optional.of(refreshToken));
+
+ // When/Then
+ assertThatThrownBy(() -> tokenService.validateRefreshToken("refresh-token"))
+ .isInstanceOf(RuntimeException.class)
+ .hasMessage("Refresh token has expired");
+ }
+
+ @Test
+ void revokeRefreshToken_WhenValidToken_ShouldSetRevokedAt() {
+ // Given
+ when(refreshTokenRepository.findByToken("refresh-token")).thenReturn(Optional.of(refreshToken));
+ when(refreshTokenRepository.save(refreshToken)).thenReturn(refreshToken);
+
+ // When
+ tokenService.revokeRefreshToken("refresh-token");
+
+ // Then
+ assertThat(refreshToken.getRevokedAt()).isNotNull();
+ assertThat(refreshToken.getRevokedAt()).isBefore(LocalDateTime.now().plusSeconds(1));
+ verify(refreshTokenRepository).save(refreshToken);
+ }
+
+ @Test
+ void revokeRefreshToken_WhenTokenNotFound_ShouldThrowRuntimeException() {
+ // Given
+ when(refreshTokenRepository.findByToken("invalid-token")).thenReturn(Optional.empty());
+
+ // When/Then
+ assertThatThrownBy(() -> tokenService.revokeRefreshToken("invalid-token"))
+ .isInstanceOf(RuntimeException.class)
+ .hasMessage("Invalid refresh token");
+ }
+
+ @Test
+ void revokeAllUserTokens_ShouldDeleteAllUserTokens() {
+ // When
+ tokenService.revokeAllUserTokens(testUser);
+
+ // Then
+ verify(refreshTokenRepository).deleteByUser(testUser);
+ }
+
+ @Test
+ void cleanupExpiredTokens_ShouldDeleteExpiredTokens() {
+ // When
+ tokenService.cleanupExpiredTokens();
+
+ // Then
+ verify(refreshTokenRepository).deleteExpiredTokens(any(LocalDateTime.class));
+ }
+
+ @Test
+ void createVerificationToken_WithCustomExpiryHours_ShouldUseConfiguredExpiry() {
+ // Given
+ ReflectionTestUtils.setField(tokenService, "verificationExpiryHours", 48);
+ when(verificationTokenRepository.findByUserAndTokenType(testUser,
+ VerificationToken.TokenType.EMAIL_VERIFICATION))
+ .thenReturn(Optional.empty());
+ when(verificationTokenRepository.save(any(VerificationToken.class)))
+ .thenAnswer(invocation -> invocation.getArgument(0));
+
+ // When
+ String result = tokenService.createVerificationToken(testUser);
+
+ // Then
+ assertThat(result).isNotNull();
+ verify(verificationTokenRepository)
+ .save(argThat(token -> token.getExpiryDate().isAfter(LocalDateTime.now().plusHours(47))));
+ }
+
+ @Test
+ void createPasswordResetToken_WithCustomExpiryHours_ShouldUseConfiguredExpiry() {
+ // Given
+ ReflectionTestUtils.setField(tokenService, "passwordResetExpiryHours", 2);
+ when(verificationTokenRepository.findByUserAndTokenType(testUser, VerificationToken.TokenType.PASSWORD_RESET))
+ .thenReturn(Optional.empty());
+ when(verificationTokenRepository.save(any(VerificationToken.class)))
+ .thenAnswer(invocation -> invocation.getArgument(0));
+
+ // When
+ String result = tokenService.createPasswordResetToken(testUser);
+
+ // Then
+ assertThat(result).isNotNull();
+ verify(verificationTokenRepository).save(
+ argThat(token -> token.getExpiryDate().isAfter(LocalDateTime.now().plusHours(1).plusMinutes(30))));
+ }
+
+ @Test
+ void createRefreshToken_WithCustomExpiryDays_ShouldUseConfiguredExpiry() {
+ // Given
+ ReflectionTestUtils.setField(tokenService, "refreshTokenExpiryDays", 14);
+ when(refreshTokenRepository.save(any(RefreshToken.class))).thenAnswer(invocation -> invocation.getArgument(0));
+
+ // When
+ String result = tokenService.createRefreshToken(testUser, "127.0.0.1", "Test-Agent");
+
+ // Then
+ assertThat(result).isNotNull();
+ verify(refreshTokenRepository)
+ .save(argThat(token -> token.getExpiryDate().isAfter(LocalDateTime.now().plusDays(13))));
+ }
+
+ @Test
+ void validateToken_WhenTokenJustExpired_ShouldThrowRuntimeException() {
+ // Given
+ verificationToken.setExpiryDate(LocalDateTime.now().minusSeconds(1));
+ when(verificationTokenRepository.findByToken("verification-token")).thenReturn(Optional.of(verificationToken));
+
+ // When/Then
+ assertThatThrownBy(
+ () -> tokenService.validateToken("verification-token", VerificationToken.TokenType.EMAIL_VERIFICATION))
+ .isInstanceOf(RuntimeException.class)
+ .hasMessage("Token has expired");
+ }
+
+ @Test
+ void validateRefreshToken_WhenTokenJustExpired_ShouldThrowRuntimeException() {
+ // Given
+ refreshToken.setExpiryDate(LocalDateTime.now().minusSeconds(1));
+ when(refreshTokenRepository.findByToken("refresh-token")).thenReturn(Optional.of(refreshToken));
+
+ // When/Then
+ assertThatThrownBy(() -> tokenService.validateRefreshToken("refresh-token"))
+ .isInstanceOf(RuntimeException.class)
+ .hasMessage("Refresh token has expired");
+ }
+
+ @Test
+ void validateToken_WhenTokenJustBeforeExpiry_ShouldReturnToken() {
+ // Given
+ verificationToken.setExpiryDate(LocalDateTime.now().plusSeconds(1));
+ when(verificationTokenRepository.findByToken("verification-token")).thenReturn(Optional.of(verificationToken));
+
+ // When
+ VerificationToken result = tokenService.validateToken("verification-token",
+ VerificationToken.TokenType.EMAIL_VERIFICATION);
+
+ // Then
+ assertThat(result).isEqualTo(verificationToken);
+ }
+
+ @Test
+ void validateRefreshToken_WhenTokenJustBeforeExpiry_ShouldReturnToken() {
+ // Given
+ refreshToken.setExpiryDate(LocalDateTime.now().plusSeconds(1));
+ when(refreshTokenRepository.findByToken("refresh-token")).thenReturn(Optional.of(refreshToken));
+
+ // When
+ RefreshToken result = tokenService.validateRefreshToken("refresh-token");
+
+ // Then
+ assertThat(result).isEqualTo(refreshToken);
+ }
+}
diff --git a/auth-service/src/test/java/com/techtorque/auth_service/service/UserServiceTest.java b/auth-service/src/test/java/com/techtorque/auth_service/service/UserServiceTest.java
new file mode 100644
index 0000000..b0048cf
--- /dev/null
+++ b/auth-service/src/test/java/com/techtorque/auth_service/service/UserServiceTest.java
@@ -0,0 +1,704 @@
+package com.techtorque.auth_service.service;
+
+import com.techtorque.auth_service.entity.Role;
+import com.techtorque.auth_service.entity.RoleName;
+import com.techtorque.auth_service.entity.User;
+import com.techtorque.auth_service.repository.*;
+import jakarta.persistence.EntityNotFoundException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+import java.util.*;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * Comprehensive test class for UserService
+ * Tests user management, security, and role/permission operations
+ */
+@ExtendWith(MockitoExtension.class)
+class UserServiceTest {
+
+ @Mock
+ private UserRepository userRepository;
+
+ @Mock
+ private RoleRepository roleRepository;
+
+ @Mock
+ private PasswordEncoder passwordEncoder;
+
+ @Mock
+ private LoginLockRepository loginLockRepository;
+
+ @Mock
+ private RefreshTokenRepository refreshTokenRepository;
+
+ @Mock
+ private VerificationTokenRepository verificationTokenRepository;
+
+ @Mock
+ private LoginLogRepository loginLogRepository;
+
+ @Mock
+ private SecurityContext securityContext;
+
+ @Mock
+ private Authentication authentication;
+
+ @InjectMocks
+ private UserService userService;
+
+ private User testUser;
+ private Role customerRole;
+ private Role employeeRole;
+ private Role adminRole;
+ private Role superAdminRole;
+
+ @BeforeEach
+ void setUp() {
+ // Create test roles
+ customerRole = Role.builder()
+ .id(1L)
+ .name(RoleName.CUSTOMER)
+ .description("Customer role")
+ .permissions(new HashSet<>())
+ .build();
+
+ employeeRole = Role.builder()
+ .id(2L)
+ .name(RoleName.EMPLOYEE)
+ .description("Employee role")
+ .permissions(new HashSet<>())
+ .build();
+
+ adminRole = Role.builder()
+ .id(3L)
+ .name(RoleName.ADMIN)
+ .description("Admin role")
+ .permissions(new HashSet<>())
+ .build();
+
+ superAdminRole = Role.builder()
+ .id(4L)
+ .name(RoleName.SUPER_ADMIN)
+ .description("Super Admin role")
+ .permissions(new HashSet<>())
+ .build();
+
+ // Create test user
+ testUser = User.builder()
+ .id(1L)
+ .username("testuser")
+ .email("test@example.com")
+ .password("encoded-password")
+ .fullName("Test User")
+ .phone("1234567890")
+ .address("Test Address")
+ .enabled(true)
+ .emailVerified(true)
+ .roles(new HashSet<>(Set.of(customerRole)))
+ .build();
+ }
+
+ @Test
+ void loadUserByUsername_WhenUserFoundByUsername_ShouldReturnUserDetails() {
+ // Given
+ when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(testUser));
+
+ // When
+ UserDetails userDetails = userService.loadUserByUsername("testuser");
+
+ // Then
+ assertThat(userDetails).isNotNull();
+ assertThat(userDetails.getUsername()).isEqualTo("testuser");
+ assertThat(userDetails.getPassword()).isEqualTo("encoded-password");
+ assertThat(userDetails.isEnabled()).isTrue();
+ assertThat(userDetails.isAccountNonLocked()).isTrue();
+ assertThat(userDetails.getAuthorities()).hasSize(1);
+ assertThat(userDetails.getAuthorities()).extracting(GrantedAuthority::getAuthority)
+ .contains("ROLE_CUSTOMER");
+ }
+
+ @Test
+ void loadUserByUsername_WhenUserFoundByEmail_ShouldReturnUserDetails() {
+ // Given
+ when(userRepository.findByUsername("test@example.com")).thenReturn(Optional.empty());
+ when(userRepository.findByEmail("test@example.com")).thenReturn(Optional.of(testUser));
+
+ // When
+ UserDetails userDetails = userService.loadUserByUsername("test@example.com");
+
+ // Then
+ assertThat(userDetails).isNotNull();
+ assertThat(userDetails.getUsername()).isEqualTo("testuser");
+ verify(userRepository).findByUsername("test@example.com");
+ verify(userRepository).findByEmail("test@example.com");
+ }
+
+ @Test
+ void loadUserByUsername_WhenUserNotFound_ShouldThrowUsernameNotFoundException() {
+ // Given
+ when(userRepository.findByUsername("nonexistent")).thenReturn(Optional.empty());
+ when(userRepository.findByEmail("nonexistent")).thenReturn(Optional.empty());
+
+ // When/Then
+ assertThatThrownBy(() -> userService.loadUserByUsername("nonexistent"))
+ .isInstanceOf(UsernameNotFoundException.class)
+ .hasMessage("User not found: nonexistent");
+ }
+
+ @Test
+ void loadUserByUsername_WhenUserDisabled_ShouldReturnDisabledUserDetails() {
+ // Given
+ testUser.setEnabled(false);
+ when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(testUser));
+
+ // When
+ UserDetails userDetails = userService.loadUserByUsername("testuser");
+
+ // Then
+ assertThat(userDetails.isEnabled()).isFalse();
+ assertThat(userDetails.isAccountNonLocked()).isFalse();
+ }
+
+ @Test
+ void registerCustomer_WhenValidInput_ShouldCreateCustomerUser() {
+ // Given
+ when(userRepository.findByUsername("newuser")).thenReturn(Optional.empty());
+ when(userRepository.findByEmail("new@example.com")).thenReturn(Optional.empty());
+ when(roleRepository.findByName(RoleName.CUSTOMER)).thenReturn(Optional.of(customerRole));
+ when(passwordEncoder.encode("password")).thenReturn("encoded-password");
+ when(userRepository.save(any(User.class))).thenReturn(testUser);
+
+ // When
+ User result = userService.registerCustomer("newuser", "new@example.com", "password");
+
+ // Then
+ assertThat(result).isNotNull();
+ verify(userRepository).save(argThat(user -> user.getUsername().equals("newuser") &&
+ user.getEmail().equals("new@example.com") &&
+ user.getPassword().equals("encoded-password") &&
+ user.getEnabled() &&
+ user.getRoles().contains(customerRole)));
+ }
+
+ @Test
+ void registerCustomer_WhenUsernameExists_ShouldThrowIllegalArgumentException() {
+ // Given
+ when(userRepository.findByUsername("existinguser")).thenReturn(Optional.of(testUser));
+
+ // When/Then
+ assertThatThrownBy(() -> userService.registerCustomer("existinguser", "new@example.com", "password"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Username already exists: existinguser");
+ }
+
+ @Test
+ void registerCustomer_WhenEmailExists_ShouldThrowIllegalArgumentException() {
+ // Given
+ when(userRepository.findByUsername("newuser")).thenReturn(Optional.empty());
+ when(userRepository.findByEmail("existing@example.com")).thenReturn(Optional.of(testUser));
+
+ // When/Then
+ assertThatThrownBy(() -> userService.registerCustomer("newuser", "existing@example.com", "password"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Email already exists: existing@example.com");
+ }
+
+ @Test
+ void registerCustomer_WhenCustomerRoleNotFound_ShouldThrowEntityNotFoundException() {
+ // Given
+ when(userRepository.findByUsername("newuser")).thenReturn(Optional.empty());
+ when(userRepository.findByEmail("new@example.com")).thenReturn(Optional.empty());
+ when(roleRepository.findByName(RoleName.CUSTOMER)).thenReturn(Optional.empty());
+
+ // When/Then
+ assertThatThrownBy(() -> userService.registerCustomer("newuser", "new@example.com", "password"))
+ .isInstanceOf(EntityNotFoundException.class)
+ .hasMessage("Customer role not found");
+ }
+
+ @Test
+ void createEmployee_WhenValidInput_ShouldCreateEmployeeUser() {
+ // Given
+ when(userRepository.findByUsername("employee")).thenReturn(Optional.empty());
+ when(userRepository.findByEmail("employee@example.com")).thenReturn(Optional.empty());
+ when(roleRepository.findByName(RoleName.EMPLOYEE)).thenReturn(Optional.of(employeeRole));
+ when(passwordEncoder.encode("password")).thenReturn("encoded-password");
+ when(userRepository.save(any(User.class))).thenReturn(testUser);
+
+ // When
+ User result = userService.createEmployee("employee", "employee@example.com", "password", null);
+
+ // Then
+ assertThat(result).isNotNull();
+ verify(userRepository).save(argThat(user -> user.getUsername().equals("employee") &&
+ user.getEmail().equals("employee@example.com") &&
+ user.getRoles().contains(employeeRole)));
+ }
+
+ @Test
+ void createAdmin_WhenValidInput_ShouldCreateAdminUser() {
+ // Given
+ when(userRepository.findByUsername("admin")).thenReturn(Optional.empty());
+ when(userRepository.findByEmail("admin@example.com")).thenReturn(Optional.empty());
+ when(roleRepository.findByName(RoleName.ADMIN)).thenReturn(Optional.of(adminRole));
+ when(passwordEncoder.encode("password")).thenReturn("encoded-password");
+ when(userRepository.save(any(User.class))).thenReturn(testUser);
+
+ // When
+ User result = userService.createAdmin("admin", "admin@example.com", "password", null);
+
+ // Then
+ assertThat(result).isNotNull();
+ verify(userRepository).save(argThat(user -> user.getUsername().equals("admin") &&
+ user.getEmail().equals("admin@example.com") &&
+ user.getRoles().contains(adminRole)));
+ }
+
+ @Test
+ void findByUsername_WhenUserExists_ShouldReturnUser() {
+ // Given
+ when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(testUser));
+
+ // When
+ Optional result = userService.findByUsername("testuser");
+
+ // Then
+ assertThat(result).isPresent();
+ assertThat(result.get()).isEqualTo(testUser);
+ }
+
+ @Test
+ void findByEmail_WhenUserExists_ShouldReturnUser() {
+ // Given
+ when(userRepository.findByEmail("test@example.com")).thenReturn(Optional.of(testUser));
+
+ // When
+ Optional result = userService.findByEmail("test@example.com");
+
+ // Then
+ assertThat(result).isPresent();
+ assertThat(result.get()).isEqualTo(testUser);
+ }
+
+ @Test
+ void findAllUsers_ShouldReturnAllUsers() {
+ // Given
+ List users = List.of(testUser);
+ when(userRepository.findAll()).thenReturn(users);
+
+ // When
+ List result = userService.findAllUsers();
+
+ // Then
+ assertThat(result).isEqualTo(users);
+ }
+
+ @Test
+ void getUserPermissions_WhenUserExists_ShouldReturnPermissions() {
+ // Given
+ when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(testUser));
+
+ // When
+ Set permissions = userService.getUserPermissions("testuser");
+
+ // Then
+ assertThat(permissions).isNotNull();
+ // Note: permissions are empty in our test setup, so this just verifies the
+ // method works
+ }
+
+ @Test
+ void getUserPermissions_WhenUserNotFound_ShouldThrowEntityNotFoundException() {
+ // Given
+ when(userRepository.findByUsername("nonexistent")).thenReturn(Optional.empty());
+
+ // When/Then
+ assertThatThrownBy(() -> userService.getUserPermissions("nonexistent"))
+ .isInstanceOf(EntityNotFoundException.class)
+ .hasMessage("User not found: nonexistent");
+ }
+
+ @Test
+ void getUserRoles_WhenUserExists_ShouldReturnRoles() {
+ // Given
+ when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(testUser));
+
+ // When
+ Set roles = userService.getUserRoles("testuser");
+
+ // Then
+ assertThat(roles).contains("CUSTOMER");
+ }
+
+ @Test
+ void enableUser_WhenUserExists_ShouldEnableUser() {
+ // Given
+ testUser.setEnabled(false);
+ when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(testUser));
+ when(userRepository.save(testUser)).thenReturn(testUser);
+
+ // When
+ userService.enableUser("testuser");
+
+ // Then
+ assertThat(testUser.getEnabled()).isTrue();
+ verify(userRepository).save(testUser);
+ }
+
+ @Test
+ void disableUser_WhenUserExists_ShouldDisableUser() {
+ // Given
+ when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(testUser));
+ when(userRepository.save(testUser)).thenReturn(testUser);
+
+ // When
+ userService.disableUser("testuser");
+
+ // Then
+ assertThat(testUser.getEnabled()).isFalse();
+ verify(userRepository).save(testUser);
+ }
+
+ @Test
+ void deleteUser_WhenUserExists_ShouldDeleteUserAndRelatedData() {
+ // Given
+ when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(testUser));
+
+ // When
+ userService.deleteUser("testuser");
+
+ // Then
+ verify(userRepository).save(testUser); // Clear roles
+ verify(refreshTokenRepository).deleteByUser(testUser);
+ verify(verificationTokenRepository).deleteByUser(testUser);
+ verify(loginLockRepository).deleteByUsername("testuser");
+ verify(loginLogRepository).deleteByUsername("testuser");
+ verify(userRepository).delete(testUser);
+ }
+
+ @Test
+ void deleteUser_WhenUserNotFound_ShouldThrowEntityNotFoundException() {
+ // Given
+ when(userRepository.findByUsername("nonexistent")).thenReturn(Optional.empty());
+
+ // When/Then
+ assertThatThrownBy(() -> userService.deleteUser("nonexistent"))
+ .isInstanceOf(EntityNotFoundException.class)
+ .hasMessage("User not found: nonexistent");
+ }
+
+ @Test
+ void clearLoginLock_WhenLockExists_ShouldClearLock() {
+ // Given
+ com.techtorque.auth_service.entity.LoginLock loginLock = com.techtorque.auth_service.entity.LoginLock.builder()
+ .username("testuser")
+ .failedAttempts(3)
+ .lockUntil(java.time.LocalDateTime.now().plusMinutes(15))
+ .build();
+ when(loginLockRepository.findByUsername("testuser")).thenReturn(Optional.of(loginLock));
+
+ // When
+ userService.clearLoginLock("testuser");
+
+ // Then
+ assertThat(loginLock.getFailedAttempts()).isEqualTo(0);
+ assertThat(loginLock.getLockUntil()).isNull();
+ verify(loginLockRepository).save(loginLock);
+ }
+
+ @Test
+ void clearLoginLock_WhenLockNotExists_ShouldNotThrowException() {
+ // Given
+ when(loginLockRepository.findByUsername("testuser")).thenReturn(Optional.empty());
+
+ // When/Then
+ assertThatCode(() -> userService.clearLoginLock("testuser"))
+ .doesNotThrowAnyException();
+ }
+
+ @Test
+ void hasRole_WhenUserHasRole_ShouldReturnTrue() {
+ // Given
+ when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(testUser));
+
+ // When
+ boolean result = userService.hasRole("testuser", RoleName.CUSTOMER);
+
+ // Then
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ void hasRole_WhenUserDoesNotHaveRole_ShouldReturnFalse() {
+ // Given
+ when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(testUser));
+
+ // When
+ boolean result = userService.hasRole("testuser", RoleName.ADMIN);
+
+ // Then
+ assertThat(result).isFalse();
+ }
+
+ @Test
+ void hasPermission_WhenUserHasPermission_ShouldReturnTrue() {
+ // Given
+ com.techtorque.auth_service.entity.Permission permission = com.techtorque.auth_service.entity.Permission
+ .builder()
+ .name("READ_USERS")
+ .build();
+ customerRole.getPermissions().add(permission);
+ when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(testUser));
+
+ // When
+ boolean result = userService.hasPermission("testuser", "READ_USERS");
+
+ // Then
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ void hasPermission_WhenUserDoesNotHavePermission_ShouldReturnFalse() {
+ // Given
+ when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(testUser));
+
+ // When
+ boolean result = userService.hasPermission("testuser", "DELETE_USERS");
+
+ // Then
+ assertThat(result).isFalse();
+ }
+
+ @Test
+ void updateUserDetails_WhenValidInput_ShouldUpdateUser() {
+ // Given
+ when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(testUser));
+ when(userRepository.existsByUsername("newusername")).thenReturn(false);
+ when(userRepository.existsByEmail("new@example.com")).thenReturn(false);
+ when(userRepository.save(testUser)).thenReturn(testUser);
+
+ // When
+ User result = userService.updateUserDetails("testuser", "newusername", "new@example.com", false);
+
+ // Then
+ assertThat(result.getUsername()).isEqualTo("newusername");
+ assertThat(result.getEmail()).isEqualTo("new@example.com");
+ assertThat(result.getEnabled()).isFalse();
+ }
+
+ @Test
+ void updateUserDetails_WhenUsernameExists_ShouldThrowIllegalArgumentException() {
+ // Given
+ when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(testUser));
+ when(userRepository.existsByUsername("existingusername")).thenReturn(true);
+
+ // When/Then
+ assertThatThrownBy(() -> userService.updateUserDetails("testuser", "existingusername", null, null))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Username already exists: existingusername");
+ }
+
+ @Test
+ void resetUserPassword_WhenUserExists_ShouldUpdatePassword() {
+ // Given
+ when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(testUser));
+ when(passwordEncoder.encode("newpassword")).thenReturn("new-encoded-password");
+ when(userRepository.save(testUser)).thenReturn(testUser);
+
+ // When
+ userService.resetUserPassword("testuser", "newpassword");
+
+ // Then
+ assertThat(testUser.getPassword()).isEqualTo("new-encoded-password");
+ verify(userRepository).save(testUser);
+ }
+
+ @Test
+ void changeUserPassword_WhenCurrentPasswordCorrect_ShouldUpdatePassword() {
+ // Given
+ when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(testUser));
+ when(passwordEncoder.matches("currentpassword", "encoded-password")).thenReturn(true);
+ when(passwordEncoder.encode("newpassword")).thenReturn("new-encoded-password");
+ when(userRepository.save(testUser)).thenReturn(testUser);
+
+ // When
+ userService.changeUserPassword("testuser", "currentpassword", "newpassword");
+
+ // Then
+ assertThat(testUser.getPassword()).isEqualTo("new-encoded-password");
+ verify(userRepository).save(testUser);
+ }
+
+ @Test
+ void changeUserPassword_WhenCurrentPasswordIncorrect_ShouldThrowIllegalStateException() {
+ // Given
+ when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(testUser));
+ when(passwordEncoder.matches("wrongpassword", "encoded-password")).thenReturn(false);
+
+ // When/Then
+ assertThatThrownBy(() -> userService.changeUserPassword("testuser", "wrongpassword", "newpassword"))
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessage("Current password is incorrect");
+ }
+
+ @Test
+ void assignRoleToUser_WhenValidRole_ShouldAssignRole() {
+ // Given
+ when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(testUser));
+ when(roleRepository.findByName(RoleName.EMPLOYEE)).thenReturn(Optional.of(employeeRole));
+ when(userRepository.save(testUser)).thenReturn(testUser);
+
+ // When
+ userService.assignRoleToUser("testuser", "EMPLOYEE");
+
+ // Then
+ assertThat(testUser.getRoles()).contains(employeeRole);
+ verify(userRepository).save(testUser);
+ }
+
+ @Test
+ void assignRoleToUser_WhenAssigningAdminRoleWithoutSuperAdminAuth_ShouldThrowAccessDeniedException() {
+ // Given
+ lenient().when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(testUser));
+ lenient().when(roleRepository.findByName(RoleName.ADMIN)).thenReturn(Optional.of(adminRole));
+
+ SecurityContextHolder.setContext(securityContext);
+ when(securityContext.getAuthentication()).thenReturn(authentication);
+ when(authentication.getAuthorities())
+ .thenReturn((Collection) java.util.Collections.singletonList(new SimpleGrantedAuthority("ROLE_ADMIN")));
+
+ // When/Then
+ assertThatThrownBy(() -> userService.assignRoleToUser("testuser", "ADMIN"))
+ .isInstanceOf(AccessDeniedException.class)
+ .hasMessage("Permission denied: Only a SUPER_ADMIN can assign the ADMIN role.");
+
+ SecurityContextHolder.clearContext();
+ }
+
+ @Test
+ void assignRoleToUser_WhenAssigningAdminRoleWithSuperAdminAuth_ShouldSucceed() {
+ // Given
+ when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(testUser));
+ when(roleRepository.findByName(RoleName.ADMIN)).thenReturn(Optional.of(adminRole));
+ when(userRepository.save(testUser)).thenReturn(testUser);
+
+ SecurityContextHolder.setContext(securityContext);
+ when(securityContext.getAuthentication()).thenReturn(authentication);
+ when(authentication.getAuthorities()).thenReturn(
+ (Collection) java.util.Collections.singletonList(new SimpleGrantedAuthority("ROLE_SUPER_ADMIN")));
+
+ // When
+ userService.assignRoleToUser("testuser", "ADMIN");
+
+ // Then
+ assertThat(testUser.getRoles()).contains(adminRole);
+ verify(userRepository).save(testUser);
+ SecurityContextHolder.clearContext();
+ }
+
+ @Test
+ void assignRoleToUser_WhenRoleAlreadyAssigned_ShouldThrowIllegalStateException() {
+ // Given
+ when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(testUser));
+ when(roleRepository.findByName(RoleName.CUSTOMER)).thenReturn(Optional.of(customerRole));
+
+ // When/Then
+ assertThatThrownBy(() -> userService.assignRoleToUser("testuser", "CUSTOMER"))
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessage("User already has role: CUSTOMER");
+ }
+
+ @Test
+ void revokeRoleFromUser_WhenValidRole_ShouldRevokeRole() {
+ // Given
+ when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(testUser));
+ when(roleRepository.findByName(RoleName.CUSTOMER)).thenReturn(Optional.of(customerRole));
+ when(userRepository.save(testUser)).thenReturn(testUser);
+
+ // When
+ userService.revokeRoleFromUser("testuser", "CUSTOMER");
+
+ // Then
+ assertThat(testUser.getRoles()).doesNotContain(customerRole);
+ verify(userRepository).save(testUser);
+ }
+
+ @Test
+ void revokeRoleFromUser_WhenRevokingOwnSuperAdminRole_ShouldThrowAccessDeniedException() {
+ // Given
+ testUser.getRoles().add(superAdminRole);
+ lenient().when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(testUser));
+ lenient().when(roleRepository.findByName(RoleName.SUPER_ADMIN)).thenReturn(Optional.of(superAdminRole));
+
+ SecurityContextHolder.setContext(securityContext);
+ when(securityContext.getAuthentication()).thenReturn(authentication);
+ when(authentication.getName()).thenReturn("testuser");
+
+ // When/Then
+ assertThatThrownBy(() -> userService.revokeRoleFromUser("testuser", "SUPER_ADMIN"))
+ .isInstanceOf(AccessDeniedException.class)
+ .hasMessage("Action denied: A SUPER_ADMIN cannot revoke their own SUPER_ADMIN role.");
+
+ SecurityContextHolder.clearContext();
+ }
+
+ @Test
+ void revokeRoleFromUser_WhenRoleNotAssigned_ShouldThrowIllegalStateException() {
+ // Given
+ when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(testUser));
+ when(roleRepository.findByName(RoleName.ADMIN)).thenReturn(Optional.of(adminRole));
+
+ // When/Then
+ assertThatThrownBy(() -> userService.revokeRoleFromUser("testuser", "ADMIN"))
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessage("User does not have role: ADMIN");
+ }
+
+ @Test
+ void updateProfile_WhenValidInput_ShouldUpdateProfile() {
+ // Given
+ when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(testUser));
+ when(userRepository.save(testUser)).thenReturn(testUser);
+
+ // When
+ User result = userService.updateProfile("testuser", "New Full Name", "9876543210", "New Address");
+
+ // Then
+ assertThat(result.getFullName()).isEqualTo("New Full Name");
+ assertThat(result.getPhone()).isEqualTo("9876543210");
+ assertThat(result.getAddress()).isEqualTo("New Address");
+ verify(userRepository).save(testUser);
+ }
+
+ @Test
+ void updateProfilePhoto_WhenValidInput_ShouldUpdatePhotoUrl() {
+ // Given
+ when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(testUser));
+ when(userRepository.save(testUser)).thenReturn(testUser);
+
+ // When
+ User result = userService.updateProfilePhoto("testuser", "http://example.com/photo.jpg");
+
+ // Then
+ assertThat(result.getProfilePhotoUrl()).isEqualTo("http://example.com/photo.jpg");
+ verify(userRepository).save(testUser);
+ }
+}