diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index 8cce08db00329781bb30fe04f6b1490ab978b19d..a74e1d6a911805b26a95eb345be549afb55b81ee 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -5,10 +5,12 @@ on: - '*' pull_request: types: [opened, synchronize, reopened] + jobs: build: name: Build and analyze runs-on: ubuntu-latest + steps: - uses: actions/checkout@v4 with: diff --git a/Dockerfile b/Dockerfile index 9372156b8f55652b70d6d0af1f7bde92551459ba..fcc2364a5f41ab24e97fdb0a084feb527cd94c45 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,6 +25,6 @@ ENV JDBC_STAGING_DATABASE_URL ${JDBC_STAGING_DATABASE_URL} ENV JDBC_STAGING_DATABASE_USERNAME ${JDBC_STAGING_DATABASE_USERNAME} WORKDIR /app -COPY --from=builder /app/target/authentication-0.0.1-SNAPSHOT.jar app.jar +COPY --from=builder /app/target/authentication-0.1.jar app.jar EXPOSE 8080 CMD ["java", "-jar", "app.jar"] diff --git a/pom.xml b/pom.xml index 10d9861a4bf1d40b33e55f5a9675d00f045a73e3..a63ccbe75f2dd4fdd2e922b756d2fa0629cd1ae7 100644 --- a/pom.xml +++ b/pom.xml @@ -47,6 +47,13 @@ <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> + <dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-dependencies</artifactId> + <version>2023.0.0</version> + <type>pom</type> + <scope>import</scope> + </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> @@ -195,11 +202,39 @@ <version>0.11.5</version> <scope>runtime</scope> </dependency> - <dependency> - <groupId>me.paulschwarz</groupId> - <artifactId>spring-dotenv</artifactId> - <version>4.0.0</version> + <groupId>com.google.api-client</groupId> + <artifactId>google-api-client</artifactId> + <version>2.6.0</version> + </dependency> + <dependency> + <groupId>com.google.http-client</groupId> + <artifactId>google-http-client-gson</artifactId> + <version>1.41.7</version> + </dependency> + <dependency> + <groupId>javax.servlet</groupId> + <artifactId>javax.servlet-api</artifactId> + <version>4.0.1</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.powermock</groupId> + <artifactId>powermock-module-junit4</artifactId> + <version>2.0.9</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.powermock</groupId> + <artifactId>powermock-api-mockito2</artifactId> + <version>2.0.9</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-inline</artifactId> + <version>5.2.0</version> <!-- Use the latest version --> + <scope>test</scope> </dependency> @@ -257,17 +292,38 @@ <version>0.8.12</version> <executions> <execution> + <id>prepare-agent</id> <goals> <goal>prepare-agent</goal> </goals> </execution> <execution> <id>report</id> - <phase>test</phase> + <phase>verify</phase> <goals> <goal>report</goal> </goals> </execution> + <execution> + <id>check</id> + <goals> + <goal>check</goal> + </goals> + <configuration> + <rules> + <rule> + <element>BUNDLE</element> + <limits> + <limit> + <counter>LINE</counter> + <value>COVEREDRATIO</value> + <minimum>1.00</minimum> + </limit> + </limits> + </rule> + </rules> + </configuration> + </execution> </executions> </plugin> </plugins> diff --git a/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java b/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java index 21a60193446684d5ac4638584ea135e6506e2321..0869e05fe92245a14d85d5640e5c1d2a91047580 100644 --- a/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java +++ b/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java @@ -5,6 +5,9 @@ import com.safetypin.authentication.exception.InvalidCredentialsException; import com.safetypin.authentication.exception.UserAlreadyExistsException; import com.safetypin.authentication.service.AuthenticationService; import jakarta.validation.Valid; +import com.safetypin.authentication.service.GoogleAuthService; +import com.safetypin.authentication.service.JwtService; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -14,9 +17,14 @@ import org.springframework.web.bind.annotation.*; public class AuthenticationController { private final AuthenticationService authenticationService; + private final GoogleAuthService googleAuthService; + private final JwtService jwtService; - public AuthenticationController(AuthenticationService authenticationService) { + @Autowired + public AuthenticationController(AuthenticationService authenticationService, GoogleAuthService googleAuthService, JwtService jwtService) { this.authenticationService = authenticationService; + this.googleAuthService = googleAuthService; + this.jwtService = jwtService; } @@ -33,19 +41,6 @@ public class AuthenticationController { } - // Endpoint for social registration/login - @PostMapping("/register-social") - public ResponseEntity<AuthResponse> registerSocial(@Valid @RequestBody SocialLoginRequest request) { - try { - String jwt = authenticationService.socialLogin(request); - return ResponseEntity.ok().body(new AuthResponse(true, "OK", new Token(jwt))); - } catch (IllegalArgumentException | UserAlreadyExistsException e) { - AuthResponse response = new AuthResponse(false, e.getMessage(), null); - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); - } - - } - // OTP verification endpoint @PostMapping("/verify-otp") public ResponseEntity<AuthResponse> verifyOTP(@RequestParam String email, @RequestParam String otp) { @@ -72,17 +67,18 @@ public class AuthenticationController { } - // Endpoint for social login (DEPRECATED, use regis-social instead) - @PostMapping("/login-social") - public ResponseEntity<AuthResponse> loginSocial(@RequestParam String email) { + @PostMapping("/google") + public ResponseEntity<AuthResponse> authenticateGoogle(@Valid @RequestBody GoogleAuthDTO googleAuthData) { try { - String jwt = authenticationService.loginSocial(email); + String jwt = googleAuthService.authenticate(googleAuthData); return ResponseEntity.ok(new AuthResponse(true, "OK", new Token(jwt))); - } catch (InvalidCredentialsException e) { + } catch (UserAlreadyExistsException e) { AuthResponse response = new AuthResponse(false, e.getMessage(), null); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); + } catch (Exception e) { + AuthResponse response = new AuthResponse(false, e.getMessage(), null); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response); } - } @@ -96,7 +92,7 @@ public class AuthenticationController { @PostMapping("/verify-jwt") public ResponseEntity<AuthResponse> verifyJwtToken(@RequestParam String token) { try { - UserResponse userResponse = authenticationService.getUserFromJwtToken(token); + UserResponse userResponse = jwtService.getUserFromJwtToken(token); return ResponseEntity.ok(new AuthResponse(true, "OK", userResponse)); } catch (InvalidCredentialsException e) { AuthResponse response = new AuthResponse(false, e.getMessage(), null); @@ -104,13 +100,6 @@ public class AuthenticationController { } } - - // Endpoint simulating a content post that requires a verified account (DEPRECATED, use be-post instead) - @PostMapping("/post") - public String postContent(@RequestParam String email, @RequestParam String content) { - return authenticationService.postContent(email, content); - } - // On successful login, return an empty map as a placeholder for future reports @GetMapping("/dashboard") public String dashboard() { diff --git a/src/main/java/com/safetypin/authentication/dto/GoogleAuthDTO.java b/src/main/java/com/safetypin/authentication/dto/GoogleAuthDTO.java new file mode 100644 index 0000000000000000000000000000000000000000..e99d997d6852fee4a2c42fe022bde21a5e7c0f81 --- /dev/null +++ b/src/main/java/com/safetypin/authentication/dto/GoogleAuthDTO.java @@ -0,0 +1,20 @@ +package com.safetypin.authentication.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Setter +@Getter +@EqualsAndHashCode +@ToString +public class GoogleAuthDTO { + + @NotBlank + private String idToken; + + @NotBlank + private String serverAuthCode; +} \ No newline at end of file diff --git a/src/main/java/com/safetypin/authentication/exception/ApiException.java b/src/main/java/com/safetypin/authentication/exception/ApiException.java new file mode 100644 index 0000000000000000000000000000000000000000..7a5d80ea764d6e2cab580fdc04f864433077fd4e --- /dev/null +++ b/src/main/java/com/safetypin/authentication/exception/ApiException.java @@ -0,0 +1,11 @@ +package com.safetypin.authentication.exception; + +import java.io.IOException; + +public class ApiException extends IOException { + + public ApiException(String message) { + super(message); + } + +} \ No newline at end of file diff --git a/src/main/java/com/safetypin/authentication/model/User.java b/src/main/java/com/safetypin/authentication/model/User.java index baba8721fd0ccb144ea8e33362b1450271d7179e..7fd5f65f99ed01e7058afb07ad8b8dbb267984c2 100644 --- a/src/main/java/com/safetypin/authentication/model/User.java +++ b/src/main/java/com/safetypin/authentication/model/User.java @@ -52,10 +52,6 @@ public class User { @Getter private String provider; // "EMAIL", "GOOGLE", "APPLE" - @Setter - @Getter - private String socialId; // For social login users - // Getters and setters diff --git a/src/main/java/com/safetypin/authentication/security/SecurityConfig.java b/src/main/java/com/safetypin/authentication/security/SecurityConfig.java index cf7421e2be4860288392ae21398d38e262c6edf7..20ab02f50ce8dd3852704388e9dda1f1ec602132 100644 --- a/src/main/java/com/safetypin/authentication/security/SecurityConfig.java +++ b/src/main/java/com/safetypin/authentication/security/SecurityConfig.java @@ -16,7 +16,7 @@ public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http - // CSRF protection is enabled by default, so we don't disable it here + .csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests(auth -> auth .requestMatchers("/**").permitAll() // Allow all requests ) diff --git a/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java b/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java index 54da05b3adddbd15bd5708ce635c3b72d9b57084..2018c87adba7ba92f8d4c5c454b19c8348e5281a 100644 --- a/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java +++ b/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java @@ -42,7 +42,6 @@ public class DevDataSeeder implements Runnable { user1.setRole(Role.REGISTERED_USER); user1.setBirthdate(LocalDate.of(1990, 1, 1)); user1.setProvider(EMAIL_PROVIDER); - user1.setSocialId("social1"); userRepository.save(user1); User user2 = new User(); @@ -53,7 +52,6 @@ public class DevDataSeeder implements Runnable { user2.setRole(Role.REGISTERED_USER); user2.setBirthdate(LocalDate.of(1991, 2, 2)); user2.setProvider(EMAIL_PROVIDER); - user2.setSocialId("social2"); userRepository.save(user2); @@ -65,7 +63,6 @@ public class DevDataSeeder implements Runnable { user3.setRole(Role.REGISTERED_USER); user3.setBirthdate(LocalDate.of(1992, 3, 3)); user3.setProvider(EMAIL_PROVIDER); - user3.setSocialId("social3"); userRepository.save(user3); User user4 = new User(); @@ -76,7 +73,6 @@ public class DevDataSeeder implements Runnable { user4.setRole(Role.REGISTERED_USER); user4.setBirthdate(LocalDate.of(1993, 4, 4)); user4.setProvider(EMAIL_PROVIDER); - user4.setSocialId("social4"); userRepository.save(user4); User user5 = new User(); @@ -87,7 +83,6 @@ public class DevDataSeeder implements Runnable { user5.setRole(Role.PREMIUM_USER); user5.setBirthdate(LocalDate.of(1994, 5, 5)); user5.setProvider(EMAIL_PROVIDER); - user5.setSocialId("social5"); userRepository.save(user5); User user6 = new User(); diff --git a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java index 48d9ea81d3edd85a241334b8fa2b7e2c8b633b8c..064d215910db7c568e17df7567b26a51c294da1e 100644 --- a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java +++ b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java @@ -1,46 +1,35 @@ package com.safetypin.authentication.service; import com.safetypin.authentication.dto.RegistrationRequest; -import com.safetypin.authentication.dto.SocialLoginRequest; -import com.safetypin.authentication.dto.UserResponse; import com.safetypin.authentication.exception.InvalidCredentialsException; import com.safetypin.authentication.exception.UserAlreadyExistsException; import com.safetypin.authentication.model.Role; import com.safetypin.authentication.model.User; -import com.safetypin.authentication.repository.UserRepository; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.JwtException; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import io.jsonwebtoken.security.Keys; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; -import java.security.Key; import java.time.LocalDate; import java.time.Period; -import java.util.Date; import java.util.Optional; -import java.util.UUID; @Service public class AuthenticationService { public static final String EMAIL_PROVIDER = "EMAIL"; - private static final Logger logger = LoggerFactory.getLogger(AuthenticationService.class); - @Value("${JWT_SECRET_KEY}") - private String JWT_SECRET_KEY; - private static final Long JWT_TOKEN_EXPIRATION_TIME = 86400000L; - private final UserRepository userRepository; + + private final UserService userService; private final PasswordEncoder passwordEncoder; private final OTPService otpService; - public AuthenticationService(UserRepository userRepository, PasswordEncoder passwordEncoder, OTPService otpService) { - this.userRepository = userRepository; + private final JwtService jwtService; + private static final Logger logger = LoggerFactory.getLogger(AuthenticationService.class); + + public AuthenticationService(UserService userService, PasswordEncoder passwordEncoder, OTPService otpService, JwtService jwtService) { + this.userService = userService; this.passwordEncoder = passwordEncoder; this.otpService = otpService; + this.jwtService = jwtService; } // Registration using email – includes birthdate and OTP generation @@ -48,8 +37,8 @@ public class AuthenticationService { if (calculateAge(request.getBirthdate()) < 16) { throw new IllegalArgumentException("User must be at least 16 years old"); } - User existingUser = userRepository.findByEmail(request.getEmail()); - if (existingUser != null) { + Optional<User> existingUser = userService.findByEmail(request.getEmail()); + if (existingUser.isPresent()) { throw new UserAlreadyExistsException("Email address is already registered. If you previously used social login (Google/Apple), please use that method to sign in."); } String encodedPassword = passwordEncoder.encode(request.getPassword()); @@ -63,84 +52,41 @@ public class AuthenticationService { user.setRole(Role.REGISTERED_USER); user.setBirthdate(request.getBirthdate()); user.setProvider(EMAIL_PROVIDER); - user.setSocialId(null); - user = userRepository.save(user); + user = userService.save(user); otpService.generateOTP(request.getEmail()); logger.info("OTP generated for user at {}", java.time.LocalDateTime.now()); - return generateJwtToken(user.getId()); - } - - // Social registration/login – simulating data fetched from Google/Apple - public String socialLogin(SocialLoginRequest request) { - if (calculateAge(request.getBirthdate()) < 16) { - throw new IllegalArgumentException("User must be at least 16 years old"); - } - User existing = userRepository.findByEmail(request.getEmail()); - - // login (if email is found) - if (existing != null) { - if (EMAIL_PROVIDER.equals(existing.getProvider())) { - // already logged in using email instead of social - throw new UserAlreadyExistsException("An account with this email exists. Please sign in using your email and password."); - } - generateJwtToken(existing.getId()); - } - - // register - User user = new User(); - user.setEmail(request.getEmail()); - user.setPassword(null); - user.setName(request.getName()); - user.setVerified(true); - user.setRole(Role.REGISTERED_USER); - user.setBirthdate(request.getBirthdate()); - user.setProvider(request.getProvider().toUpperCase()); - user.setSocialId(request.getSocialId()); - - user = userRepository.save(user); - logger.info("User registered via social login at {}", java.time.LocalDateTime.now()); - - return generateJwtToken(user.getId()); + return jwtService.generateToken(user.getId()); } // Email login with detailed error messages public String loginUser(String email, String rawPassword) { - User user = userRepository.findByEmail(email); - if (user == null) { + Optional<User> findUser = userService.findByEmail(email); + if (findUser.isEmpty()) { // email not exists logger.warn("Login failed: Email not found"); throw new InvalidCredentialsException("Invalid email"); } + User user = findUser.get(); if (!passwordEncoder.matches(rawPassword, user.getPassword())) { // incorrect password logger.warn("Login failed: Incorrect password attempt"); throw new InvalidCredentialsException("Invalid password"); } logger.info("User logged in at {}", java.time.LocalDateTime.now()); - return generateJwtToken(user.getId()); - } - - // Social login verification (assumed to be pre-verified externally) - public String loginSocial(String email) { - User user = userRepository.findByEmail(email); - if (user == null) { - throw new InvalidCredentialsException("Social login failed: Email not found"); - } - logger.info("User logged in via social authentication at {}", java.time.LocalDateTime.now()); - - return generateJwtToken(user.getId()); + return jwtService.generateToken(user.getId()); } // OTP verification – marks user as verified upon success public boolean verifyOTP(String email, String otp) { boolean result = otpService.verifyOTP(email, otp); if (result) { - User user = userRepository.findByEmail(email); - if (user != null) { + Optional<User> findUser = userService.findByEmail(email); + if (findUser.isPresent()) { + User user = findUser.get(); user.setVerified(true); - userRepository.save(user); + userService.save(user); logger.info("OTP successfully verified at {}", java.time.LocalDateTime.now()); } } else { @@ -151,73 +97,17 @@ public class AuthenticationService { // Forgot password – only applicable for email-registered users public void forgotPassword(String email) { - User user = userRepository.findByEmail(email); - if (user == null || !EMAIL_PROVIDER.equals(user.getProvider())) { + + Optional<User> user = userService.findByEmail(email); + if (user.isEmpty() || !EMAIL_PROVIDER.equals(user.get().getProvider())) { throw new IllegalArgumentException("Password reset is only available for email-registered users."); } // In production, send a reset token via email. logger.info("Password reset requested at {}", java.time.LocalDateTime.now()); } - // Example method representing posting content that requires a verified account - // Deprecated : moved to be-post - public String postContent(String email, String content) { // NOSONAR - User user = userRepository.findByEmail(email); - if (user == null) { - return "User not found. Please register."; - } - if (!user.isVerified()) { - return "Your account is not verified. Please complete OTP verification. You may request a new OTP after 2 minutes."; - } - logger.info("Content posted successfully by user"); - // For demo purposes, we assume the post is successful. - return "Content posted successfully"; - } - private int calculateAge(LocalDate birthdate) { return Period.between(birthdate, LocalDate.now()).getYears(); } - - public String generateJwtToken(UUID userId) { - Key key = Keys.hmacShaKeyFor(JWT_SECRET_KEY.getBytes()); - return Jwts - .builder() - .setSubject(userId.toString()) - .setIssuedAt(new Date(System.currentTimeMillis())) - .setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_EXPIRATION_TIME)) - .signWith(key, SignatureAlgorithm.HS256) - .compact(); - } - - public UserResponse getUserFromJwtToken(String token) { - try { - Key key = Keys.hmacShaKeyFor(JWT_SECRET_KEY.getBytes()); - - Claims claims = Jwts.parserBuilder() - .setSigningKey(key) - .build() - .parseClaimsJws(token) - .getBody(); - - boolean isExpired = claims.getExpiration().before(new Date(System.currentTimeMillis())); - UUID userId = UUID.fromString(claims.getSubject()); - - if (isExpired) { - throw new InvalidCredentialsException("Token expired"); - } - - Optional<User> user = userRepository.findById(userId); - if (user.isEmpty()) { - throw new InvalidCredentialsException("User not found"); - } - return user.get().generateUserResponse(); - - } catch (JwtException | IllegalArgumentException e){ - throw new InvalidCredentialsException("Invalid token"); - } - - } - - } diff --git a/src/main/java/com/safetypin/authentication/service/GoogleAuthService.java b/src/main/java/com/safetypin/authentication/service/GoogleAuthService.java new file mode 100644 index 0000000000000000000000000000000000000000..ce8ef7ad860c7fe5af0ffade7017a4dcfd264664 --- /dev/null +++ b/src/main/java/com/safetypin/authentication/service/GoogleAuthService.java @@ -0,0 +1,217 @@ +package com.safetypin.authentication.service; + +import com.google.api.client.auth.oauth2.TokenResponse; +import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeTokenRequest; +import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken; +import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier; +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.json.gson.GsonFactory; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import com.safetypin.authentication.dto.GoogleAuthDTO; +import com.safetypin.authentication.exception.ApiException; +import com.safetypin.authentication.exception.InvalidCredentialsException; +import com.safetypin.authentication.exception.UserAlreadyExistsException; +import com.safetypin.authentication.model.Role; +import com.safetypin.authentication.model.User; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.io.*; +import java.net.*; +import java.security.GeneralSecurityException; +import java.time.LocalDate; +import java.time.Year; +import java.util.Collections; +import java.util.Optional; + +@Service +public class GoogleAuthService { + private static final Logger logger = LoggerFactory.getLogger(GoogleAuthService.class); + + private final UserService userService; + private final JwtService jwtService; + + @Value("${google.client.id:default}") + private String googleClientId; + + @Value("${google.client.secret:default}") + private String googleClientSecret; + + private static final String EMAIL_PROVIDER = "GOOGLE"; + private static final String PEOPLE_API_BASE_URL = "https://people.googleapis.com/v1/people/me"; + + private static final String BIRTHDAY = "birthdays"; + + public GoogleAuthService(UserService userService, JwtService jwtService) { + this.userService = userService; + this.jwtService = jwtService; + } + + public String authenticate(GoogleAuthDTO googleAuthDTO) throws ApiException { + try { + GoogleIdToken.Payload payload = verifyIdToken(googleAuthDTO.getIdToken()); + String email = payload.getEmail(); + String name = (String) payload.get("name"); + + Optional<User> existingUser = userService.findByEmail(email); + if (existingUser.isPresent()) { + User user = existingUser.get(); + String userProvider = user.getProvider(); + if (!EMAIL_PROVIDER.equals(userProvider)) { + throw new UserAlreadyExistsException("An account with this email exists. Please sign in using " + userProvider); + } + return jwtService.generateToken(user.getId()); + } + + String accessToken = getAccessToken(googleAuthDTO.getServerAuthCode()); + LocalDate userBirthdate = getUserBirthdate(accessToken); + + User newUser = new User(); + newUser.setEmail(email); + newUser.setName(name); + newUser.setPassword(null); + newUser.setProvider(EMAIL_PROVIDER); + newUser.setVerified(true); + newUser.setRole(Role.REGISTERED_USER); + newUser.setBirthdate(userBirthdate); + + User user = userService.save(newUser); + logger.info("New user registered via Google authentication: {}", email); + + return jwtService.generateToken(user.getId()); + } catch (UserAlreadyExistsException e) { + throw e; + } catch (Exception e) { + logger.error("Google authentication failed: {}", e.getMessage()); + throw new ApiException("Authentication failed"); + } + } + + GoogleIdTokenVerifier createIdTokenVerifier() { + return new GoogleIdTokenVerifier.Builder( + new NetHttpTransport(), new GsonFactory()) + .setAudience(Collections.singletonList(googleClientId)) + .build(); + } + + GoogleAuthorizationCodeTokenRequest createAuthorizationCodeTokenRequest(String serverAuthCode) { + return new GoogleAuthorizationCodeTokenRequest( + new NetHttpTransport(), + GsonFactory.getDefaultInstance(), + "https://oauth2.googleapis.com/token", + googleClientId, + googleClientSecret, + serverAuthCode, + ""); + } + + GoogleIdToken.Payload verifyIdToken(String idTokenString) throws IllegalArgumentException, InvalidCredentialsException, GeneralSecurityException, IOException { + if (idTokenString == null) { + throw new IllegalArgumentException("ID Token cannot be null"); + } + + GoogleIdTokenVerifier verifier = createIdTokenVerifier(); + GoogleIdToken idToken = verifier.verify(idTokenString); + + if (idToken == null) { + throw new InvalidCredentialsException("Invalid ID Token"); + } + return idToken.getPayload(); + } + + protected URL createURL(String urlString) throws IOException { + URI uri = URI.create(urlString); + return uri.toURL(); + } + + String getAccessToken(String serverAuthCode) throws IOException { + TokenResponse tokenResponse = createAuthorizationCodeTokenRequest(serverAuthCode) + .execute(); + + return tokenResponse.getAccessToken(); + } + + String fetchUserData(String accessToken) { + try { + String apiUrl = PEOPLE_API_BASE_URL + "?personFields=" + GoogleAuthService.BIRTHDAY; + URL url = createURL(apiUrl); + + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + try { + conn.setRequestMethod("GET"); + conn.setRequestProperty("Authorization", "Bearer " + accessToken); + conn.setRequestProperty("Accept", "application/json"); + + int responseCode = conn.getResponseCode(); + if (responseCode == HttpURLConnection.HTTP_OK) { + return readResponse(conn.getInputStream()); + } else { + logger.error("Error fetching data from Google API: HTTP {}", responseCode); + return null; + } + } finally { + conn.disconnect(); + } + } catch (MalformedURLException e) { + logger.error("Invalid API URL", e); + return null; + } catch (IOException e) { + logger.error("IO error when fetching user data", e); + return null; + } + } + + + private String readResponse(InputStream inputStream) throws IOException { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { + StringBuilder response = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + response.append(line); + } + return response.toString(); + } + } + + LocalDate getUserBirthdate(String accessToken) { + String response = fetchUserData(accessToken); + return extractBirthday(response); + } + + LocalDate extractBirthday(String jsonResponse) { + JsonElement rootElement = JsonParser.parseString(jsonResponse); + JsonObject rootObj = rootElement.getAsJsonObject(); + + if (!rootObj.has(BIRTHDAY)) { + return null; + } + + JsonArray birthdaysArray = rootObj.getAsJsonArray(BIRTHDAY); + if (birthdaysArray.isEmpty()) { + return null; + } + + JsonObject birthdayObj = birthdaysArray.get(0).getAsJsonObject(); + if (!birthdayObj.has("date")) { + return null; + } + + JsonObject dateObj = birthdayObj.getAsJsonObject("date"); + + int year = dateObj.has("year") ? dateObj.get("year").getAsInt() : 0; + int month = dateObj.get("month").getAsInt(); + int day = dateObj.get("day").getAsInt(); + + if (year > 0) { + return LocalDate.of(year, month, day); + } else { + return LocalDate.of(Year.now().getValue(), month, day); + } + } +} diff --git a/src/main/java/com/safetypin/authentication/service/JwtService.java b/src/main/java/com/safetypin/authentication/service/JwtService.java new file mode 100644 index 0000000000000000000000000000000000000000..e312b56b08533522f8c47808cc1f1556ef765fa0 --- /dev/null +++ b/src/main/java/com/safetypin/authentication/service/JwtService.java @@ -0,0 +1,62 @@ +package com.safetypin.authentication.service; + +import com.safetypin.authentication.dto.UserResponse; +import com.safetypin.authentication.exception.InvalidCredentialsException; +import com.safetypin.authentication.model.User; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import org.springframework.stereotype.Service; +import org.springframework.beans.factory.annotation.Value; + +import java.security.Key; +import java.util.Date; +import java.util.UUID; + +@Service +public class JwtService { + private static final long EXPIRATION_TIME = 86400000; // 1 day + + private final Key key; + + private final UserService userService; + + public JwtService(@Value("${jwt.secret:biggerboysandstolensweethearts}") String secretKey, UserService userService) { + this.key = Keys.hmacShaKeyFor(secretKey.getBytes()); + this.userService = userService; + } + + public String generateToken(UUID userId) { + return Jwts.builder() + .setSubject(userId.toString()) + .setIssuedAt(new Date(System.currentTimeMillis())) + .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) + .signWith(key, SignatureAlgorithm.HS256) + .compact(); + } + + public Claims parseToken(String token) { + return Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(token) + .getBody(); + } + + public UserResponse getUserFromJwtToken(String token) throws InvalidCredentialsException { + Claims claims = parseToken(token); + + boolean isExpired = claims.getExpiration().before(new Date(System.currentTimeMillis())); + UUID userId = UUID.fromString(claims.getSubject()); + + if (isExpired) { + throw new InvalidCredentialsException("Token expired"); + } else { + User user = userService.findById(userId) + .orElseThrow(() -> new InvalidCredentialsException("User not found")); + + return user.generateUserResponse(); + } + } +} diff --git a/src/main/java/com/safetypin/authentication/service/UserService.java b/src/main/java/com/safetypin/authentication/service/UserService.java new file mode 100644 index 0000000000000000000000000000000000000000..8a17e3ab4106fa5de071ca5dcae6c34143eef812 --- /dev/null +++ b/src/main/java/com/safetypin/authentication/service/UserService.java @@ -0,0 +1,29 @@ +package com.safetypin.authentication.service; + +import com.safetypin.authentication.model.User; +import com.safetypin.authentication.repository.UserRepository; +import org.springframework.stereotype.Service; + +import java.util.Optional; +import java.util.UUID; + +@Service +public class UserService { + private final UserRepository userRepository; + + public UserService(UserRepository userRepository) { + this.userRepository = userRepository; + } + + public Optional<User> findById(UUID id) { + return userRepository.findById(id); + } + + public Optional<User> findByEmail(String email) { + return Optional.ofNullable(userRepository.findByEmail(email)); + } + + public User save(User user) { + return userRepository.save(user); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 9b8952e3e1f63f29ec58126023430b22ccad6930..1f57723b9a70ad73a3d55c4e9401aa7f9b34885a 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -10,4 +10,6 @@ spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.starttls.enable=true spring.mail.properties.mail.smtp.starttls.required=true -JWT_SECRET_KEY={$JWT_SECRET_KEY} \ No newline at end of file +google.client.id=${GOOGLE_CLIENT_ID:default} +google.client.secret=${GOOGLE_CLIENT_SECRET:default} +jwt.secret=${JWT_SECRET:biggerboysandstolensweetheartsss} \ No newline at end of file diff --git a/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java b/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java index b847fbf259a73ddff0ab5f3e9af9aebbcd5c9a7f..3002d32c3d68eae6b6713d7fe98bd541c5838547 100644 --- a/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java +++ b/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java @@ -1,14 +1,17 @@ package com.safetypin.authentication.controller; import com.fasterxml.jackson.databind.ObjectMapper; +import com.safetypin.authentication.dto.GoogleAuthDTO; import com.safetypin.authentication.dto.PasswordResetRequest; import com.safetypin.authentication.dto.RegistrationRequest; -import com.safetypin.authentication.dto.SocialLoginRequest; import com.safetypin.authentication.dto.UserResponse; import com.safetypin.authentication.exception.InvalidCredentialsException; import com.safetypin.authentication.model.Role; +import com.safetypin.authentication.exception.UserAlreadyExistsException; import com.safetypin.authentication.model.User; import com.safetypin.authentication.service.AuthenticationService; +import com.safetypin.authentication.service.GoogleAuthService; +import com.safetypin.authentication.service.JwtService; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; @@ -40,9 +43,44 @@ class AuthenticationControllerTest { @Autowired private AuthenticationService authenticationService; + @Autowired + private GoogleAuthService googleAuthService; + + @Autowired + private JwtService jwtService; + @Autowired private ObjectMapper objectMapper; + @TestConfiguration + static class TestConfig { + @Bean + public AuthenticationService authenticationService() { + return Mockito.mock(AuthenticationService.class); + } + + @Bean + public GoogleAuthService googleAuthService() { + return Mockito.mock(GoogleAuthService.class); + } + + @Bean + public JwtService jwtService() { + return Mockito.mock(JwtService.class); + } + } + + @TestConfiguration + static class TestSecurityConfig { + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http.csrf(AbstractHttpConfigurer::disable) + .authorizeHttpRequests(authorize -> authorize.anyRequest().permitAll()); + return http.build(); + } + } + + @Test void testRegisterEmail() throws Exception { RegistrationRequest request = new RegistrationRequest(); @@ -61,7 +99,7 @@ class AuthenticationControllerTest { UUID id = UUID.randomUUID(); user.setId(id); - String token = authenticationService.generateJwtToken(user.getId()); + String token = jwtService.generateToken(user.getId()); Mockito.when(authenticationService.registerUser(any(RegistrationRequest.class))).thenReturn(token); mockMvc.perform(post("/api/auth/register-email") @@ -71,38 +109,6 @@ class AuthenticationControllerTest { .andExpect(jsonPath("$.data.tokenValue").value(token)); } - @Test - void testRegisterSocial() throws Exception { - SocialLoginRequest request = new SocialLoginRequest(); - request.setProvider("GOOGLE"); - request.setSocialToken("token"); - request.setEmail("social@example.com"); - request.setName("Social User"); - request.setBirthdate(LocalDate.now().minusYears(25)); - request.setSocialId("social123"); - - User user = new User(); - user.setEmail("social@example.com"); - user.setPassword(null); - user.setName("Social User"); - user.setVerified(true); - user.setRole(Role.REGISTERED_USER); - user.setBirthdate(request.getBirthdate()); - user.setProvider("GOOGLE"); - user.setSocialId("social123"); - - UUID id = UUID.randomUUID(); - user.setId(id); - String token = authenticationService.generateJwtToken(user.getId()); - Mockito.when(authenticationService.socialLogin(any(SocialLoginRequest.class))).thenReturn(token); - - mockMvc.perform(post("/api/auth/register-social") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(request))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.data.tokenValue").value(token)); - } - @Test void testLoginEmail() throws Exception { User user = new User(); @@ -113,11 +119,10 @@ class AuthenticationControllerTest { user.setRole(Role.REGISTERED_USER); user.setBirthdate(LocalDate.now().minusYears(20)); user.setProvider("EMAIL"); - user.setSocialId(null); UUID id = UUID.randomUUID(); user.setId(id); - String token = authenticationService.generateJwtToken(user.getId()); + String token = jwtService.generateToken(user.getId()); Mockito.when(authenticationService.loginUser("email@example.com", "password")).thenReturn(token); mockMvc.perform(post("/api/auth/login-email") @@ -129,55 +134,24 @@ class AuthenticationControllerTest { @Test void testLoginEmail_InvalidCredentials() throws Exception { - String errorMessage = "Invalid email or password"; - Mockito.when(authenticationService.loginUser("wrong@example.com", "wrongpassword")) - .thenThrow(new InvalidCredentialsException(errorMessage)); + // Prepare test data + String email = "email@example.com"; + String password = "invalidPassword"; + // Mock the service method to throw InvalidCredentialsException + Mockito.when(authenticationService.loginUser(email, password)) + .thenThrow(new InvalidCredentialsException("Invalid email or password")); + + // Perform the test mockMvc.perform(post("/api/auth/login-email") - .param("email", "wrong@example.com") - .param("password", "wrongpassword")) + .param("email", email) + .param("password", password)) .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.success").value(false)) - .andExpect(jsonPath("$.message").value(errorMessage)) - .andExpect(jsonPath("$.data").isEmpty()); - } - - @Test - void testLoginSocial() throws Exception { - User user = new User(); - user.setEmail("social@example.com"); - user.setPassword(null); - user.setName("Social User"); - user.setVerified(true); - user.setRole(Role.REGISTERED_USER); - user.setBirthdate(LocalDate.now().minusYears(25)); - user.setProvider("GOOGLE"); - user.setSocialId("social123"); - - UUID id = UUID.randomUUID(); - user.setId(id); - String token = authenticationService.generateJwtToken(user.getId()); - Mockito.when(authenticationService.loginSocial("social@example.com")).thenReturn(token); - - mockMvc.perform(post("/api/auth/login-social") - .param("email", "social@example.com")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.data.tokenValue").value(token)); + .andExpect(jsonPath("$.message").value("Invalid email or password")) + .andExpect(jsonPath("$.data").doesNotExist()); // No data expected in case of error } - @Test - void testLoginSocial_InvalidCredentials() throws Exception { - String errorMessage = "User with this email not found"; - Mockito.when(authenticationService.loginSocial("nonexistent@example.com")) - .thenThrow(new InvalidCredentialsException(errorMessage)); - - mockMvc.perform(post("/api/auth/login-social") - .param("email", "nonexistent@example.com")) - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect(jsonPath("$.message").value(errorMessage)) - .andExpect(jsonPath("$.data").isEmpty()); - } @Test void testVerifyOTP_Success() throws Exception { @@ -228,81 +202,178 @@ class AuthenticationControllerTest { .andExpect(content().string("Password reset instructions have been sent to your email (simulated)")); } + @Test - void testVerifyJwtToken_Success() throws Exception { - String validToken = "valid.jwt.token"; - UUID userId = UUID.randomUUID(); - UserResponse userResponse = UserResponse.builder() - .id(userId) - .email("test@example.com") - .name("Test User") - .isVerified(true) - .role("REGISTERED_USER") - .birthdate(LocalDate.now().minusYears(25)) - .provider("EMAIL") - .build(); - - Mockito.when(authenticationService.getUserFromJwtToken(validToken)).thenReturn(userResponse); + void testRegisterEmail_UserAlreadyExistsException() throws Exception { + // Prepare registration request + RegistrationRequest request = new RegistrationRequest(); + request.setEmail("existing@example.com"); + request.setPassword("password"); + request.setName("Test User"); + request.setBirthdate(LocalDate.now().minusYears(20)); - mockMvc.perform(post("/api/auth/verify-jwt") - .param("token", validToken)) + // Mock the service to throw UserAlreadyExistsException + String errorMessage = "User with email already exists"; + Mockito.when(authenticationService.registerUser(Mockito.any(RegistrationRequest.class))) + .thenThrow(new UserAlreadyExistsException(errorMessage)); + + // Perform the POST request to /register-email endpoint + mockMvc.perform(post("/api/auth/register-email") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.message").value(errorMessage)) + .andExpect(jsonPath("$.data").doesNotExist()); + } + + @Test + void testDashboard() throws Exception { + mockMvc.perform(get("/api/auth/dashboard")) + .andExpect(status().isOk()) + .andExpect(content().string("{}")); + } + + @Test + void testAuthenticateGoogle_Success() throws Exception { + // Prepare test data + GoogleAuthDTO googleAuthData = new GoogleAuthDTO(); + googleAuthData.setIdToken("validGoogleToken"); + googleAuthData.setServerAuthCode("validServerAuthCode"); + + // Generate a mock JWT token + String mockJwt = "mockJwtToken123"; + + // Mock the service method to return the JWT token + Mockito.when(googleAuthService.authenticate(any(GoogleAuthDTO.class))) + .thenReturn(mockJwt); + + // Perform the test + mockMvc.perform(post("/api/auth/google") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(googleAuthData))) .andExpect(status().isOk()) .andExpect(jsonPath("$.success").value(true)) .andExpect(jsonPath("$.message").value("OK")) - .andExpect(jsonPath("$.data.email").value("test@example.com")) - .andExpect(jsonPath("$.data.name").value("Test User")); + .andExpect(jsonPath("$.data.tokenValue").value(mockJwt)); } @Test - void testVerifyJwtToken_InvalidToken() throws Exception { - String invalidToken = "invalid.token"; - String errorMessage = "Invalid or expired JWT token"; + void testAuthenticateGoogle_MissingIdToken() throws Exception { + // Prepare test data with missing idToken + GoogleAuthDTO googleAuthData = new GoogleAuthDTO(); + googleAuthData.setServerAuthCode("validServerAuthCode"); - Mockito.when(authenticationService.getUserFromJwtToken(invalidToken)) - .thenThrow(new InvalidCredentialsException(errorMessage)); + // Perform the test + mockMvc.perform(post("/api/auth/google") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(googleAuthData))) + .andExpect(status().isBadRequest()); + } - mockMvc.perform(post("/api/auth/verify-jwt") - .param("token", invalidToken)) + @Test + void testAuthenticateGoogle_MissingServerAuthCode() throws Exception { + // Prepare test data with missing serverAuthCode + GoogleAuthDTO googleAuthData = new GoogleAuthDTO(); + googleAuthData.setIdToken("validGoogleToken"); + + // Perform the test + mockMvc.perform(post("/api/auth/google") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(googleAuthData))) + .andExpect(status().isBadRequest()); + } + + @Test + void testAuthenticateGoogle_UserAlreadyExists() throws Exception { + // Prepare test data + GoogleAuthDTO googleAuthData = new GoogleAuthDTO(); + googleAuthData.setIdToken("existingUserToken"); + googleAuthData.setServerAuthCode("existingUserAuthCode"); + + // Simulate UserAlreadyExistsException + String errorMessage = "User with this email already exists"; + Mockito.when(googleAuthService.authenticate(any(GoogleAuthDTO.class))) + .thenThrow(new UserAlreadyExistsException(errorMessage)); + + // Perform the test + mockMvc.perform(post("/api/auth/google") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(googleAuthData))) .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.success").value(false)) .andExpect(jsonPath("$.message").value(errorMessage)) - .andExpect(jsonPath("$.data").isEmpty()); + .andExpect(jsonPath("$.data").doesNotExist()); } @Test - void testPostContent() throws Exception { - Mockito.when(authenticationService.postContent("email@example.com", "Test Content")) - .thenReturn("Content posted successfully"); + void testAuthenticateGoogle_GeneralException() throws Exception { + // Prepare test data + GoogleAuthDTO googleAuthData = new GoogleAuthDTO(); + googleAuthData.setIdToken("invalidToken"); + googleAuthData.setServerAuthCode("invalidAuthCode"); + + // Simulate a general exception + String errorMessage = "Authentication failed: Invalid token"; + Mockito.when(googleAuthService.authenticate(any(GoogleAuthDTO.class))) + .thenThrow(new RuntimeException(errorMessage)); + + // Perform the test + mockMvc.perform(post("/api/auth/google") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(googleAuthData))) + .andExpect(status().isInternalServerError()); + } - mockMvc.perform(post("/api/auth/post") - .param("email", "email@example.com") - .param("content", "Test Content")) - .andExpect(status().isOk()) - .andExpect(content().string("Content posted successfully")); + @Test + void testAuthenticateGoogle_EmptyInput() throws Exception { + // Perform test with empty input + mockMvc.perform(post("/api/auth/google") + .contentType(MediaType.APPLICATION_JSON) + .content("{}")) + .andExpect(status().isBadRequest()); } @Test - void testDashboard() throws Exception { - mockMvc.perform(get("/api/auth/dashboard")) - .andExpect(status().isOk()) - .andExpect(content().string("{}")); + void testVerifyJwtToken() throws Exception { + // Prepare test data + String validToken = "valid.jwt.token"; + + // Create a mock UserResponse + UserResponse userResponse = Mockito.mock(UserResponse.class); + UUID userId = UUID.randomUUID(); + + // Set up basic behavior for the mock + Mockito.when(userResponse.getId()).thenReturn(userId); + Mockito.when(userResponse.getEmail()).thenReturn("test@example.com"); + Mockito.when(userResponse.getName()).thenReturn("Test User"); + + // Mock the service method to return the mocked user response + Mockito.when(jwtService.getUserFromJwtToken(validToken)).thenReturn(userResponse); + + // Perform the test + mockMvc.perform(post("/api/auth/verify-jwt") + .param("token", validToken)) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.message").value("OK")); } - @TestConfiguration - static class TestConfig { - @Bean - public AuthenticationService authenticationService() { - return Mockito.mock(AuthenticationService.class); - } + @Test + void testVerifyJwtToken_InvalidToken() throws Exception { + // Prepare test data + String invalidToken = "invalid.jwt.token"; - @Bean - public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - http - .csrf(AbstractHttpConfigurer::disable) // Appropriate for JWT authentication - .authorizeHttpRequests(auth -> auth - .anyRequest().permitAll() - ); - return http.build(); - } + // Mock the service method to throw InvalidCredentialsException + Mockito.when(jwtService.getUserFromJwtToken(invalidToken)) + .thenThrow(new InvalidCredentialsException("Invalid token")); + + // Perform the test + mockMvc.perform(post("/api/auth/verify-jwt") + .param("token", invalidToken)) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.message").value("Invalid token")) + .andExpect(jsonPath("$.data").doesNotExist()); // No data expected in case of error } + } diff --git a/src/test/java/com/safetypin/authentication/dto/GoogleAuthDTOTest.java b/src/test/java/com/safetypin/authentication/dto/GoogleAuthDTOTest.java new file mode 100644 index 0000000000000000000000000000000000000000..c9d0095f4bed6d5bba42c7710e285122dc2c5933 --- /dev/null +++ b/src/test/java/com/safetypin/authentication/dto/GoogleAuthDTOTest.java @@ -0,0 +1,147 @@ +package com.safetypin.authentication.dto; + +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import jakarta.validation.ValidatorFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; + +class GoogleAuthDTOTest { + + private Validator validator; + + @BeforeEach + void setUp() { + try (ValidatorFactory factory = Validation.buildDefaultValidatorFactory()) { + validator = factory.getValidator(); + } + } + + @Test + void testAllArgsConstructor() { + GoogleAuthDTO dto = new GoogleAuthDTO(); + assertNotNull(dto); + } + + @Test + void testSetterAndGetterMethods() { + // Create DTO + GoogleAuthDTO dto = new GoogleAuthDTO(); + + // Set values + dto.setIdToken("test-id-token"); + dto.setServerAuthCode("test-server-auth-code"); + + // Verify getters + assertEquals("test-id-token", dto.getIdToken()); + assertEquals("test-server-auth-code", dto.getServerAuthCode()); + } + + @Test + void testValidation_AllFieldsValid() { + // Create DTO with valid fields + GoogleAuthDTO dto = new GoogleAuthDTO(); + dto.setIdToken("valid-id-token"); + dto.setServerAuthCode("valid-server-auth-code"); + + // Validate + Set<ConstraintViolation<GoogleAuthDTO>> violations = validator.validate(dto); + assertTrue(violations.isEmpty(), "No violations expected for valid input"); + } + + @Test + void testValidation_IdTokenBlank() { + // Create DTO with blank ID token + GoogleAuthDTO dto = new GoogleAuthDTO(); + dto.setIdToken(""); + dto.setServerAuthCode("valid-server-auth-code"); + + // Validate + Set<ConstraintViolation<GoogleAuthDTO>> violations = validator.validate(dto); + assertFalse(violations.isEmpty(), "Violations expected for blank ID token"); + + // Check specific violation + assertTrue(violations.stream() + .anyMatch(v -> v.getPropertyPath().toString().equals("idToken")), + "Violation should be on idToken field" + ); + } + + @Test + void testValidation_ServerAuthCodeBlank() { + // Create DTO with blank server auth code + GoogleAuthDTO dto = new GoogleAuthDTO(); + dto.setIdToken("valid-id-token"); + dto.setServerAuthCode(""); + + // Validate + Set<ConstraintViolation<GoogleAuthDTO>> violations = validator.validate(dto); + assertFalse(violations.isEmpty(), "Violations expected for blank server auth code"); + + // Check specific violation + assertTrue(violations.stream() + .anyMatch(v -> v.getPropertyPath().toString().equals("serverAuthCode")), + "Violation should be on serverAuthCode field" + ); + } + + @Test + void testValidation_BothFieldsBlank() { + // Create DTO with both fields blank + GoogleAuthDTO dto = new GoogleAuthDTO(); + dto.setIdToken(""); + dto.setServerAuthCode(""); + + // Validate + Set<ConstraintViolation<GoogleAuthDTO>> violations = validator.validate(dto); + assertFalse(violations.isEmpty(), "Violations expected when both fields are blank"); + assertEquals(2, violations.size(), "Should have violations for both fields"); + } + + @Test + void testEqualsAndHashCode() { + // Create two identical DTOs + GoogleAuthDTO dto1 = new GoogleAuthDTO(); + dto1.setIdToken("test-token"); + dto1.setServerAuthCode("test-code"); + + GoogleAuthDTO dto2 = new GoogleAuthDTO(); + dto2.setIdToken("test-token"); + dto2.setServerAuthCode("test-code"); + + GoogleAuthDTO dto3 = new GoogleAuthDTO(); + dto3.setIdToken("different-token"); + dto3.setServerAuthCode("different-code"); + + // Test equals + assertEquals(dto1, dto2, "Identical DTOs should be equal"); + assertNotEquals(dto1, dto3, "Different DTOs should not be equal"); + assertNotEquals(null, dto1, "Should not be equal to null"); + assertNotEquals(new Object(), dto1, "Should not be equal to different object type"); + + // Test hashCode + assertEquals(dto1.hashCode(), dto2.hashCode(), "Identical DTOs should have same hashCode"); + assertNotEquals(dto1.hashCode(), dto3.hashCode(), "Different DTOs should have different hashCodes"); + } + + @Test + void testToString() { + // Create DTO + GoogleAuthDTO dto = new GoogleAuthDTO(); + dto.setIdToken("test-token"); + dto.setServerAuthCode("test-code"); + + // Check toString + String toString = dto.toString(); + assertNotNull(toString); + assertTrue(toString.contains("idToken")); + assertTrue(toString.contains("serverAuthCode")); + assertTrue(toString.contains("test-token")); + assertTrue(toString.contains("test-code")); + } +} \ No newline at end of file diff --git a/src/test/java/com/safetypin/authentication/model/UserTest.java b/src/test/java/com/safetypin/authentication/model/UserTest.java index 84891ec12ef5619a680cf7f4c37419b64794a001..cfc7b2a3711b22d2ecbe63e87b40e355072e60c9 100644 --- a/src/test/java/com/safetypin/authentication/model/UserTest.java +++ b/src/test/java/com/safetypin/authentication/model/UserTest.java @@ -21,7 +21,6 @@ class UserTest { assertNull(user.getRole(), "Default role should be null"); assertNull(user.getBirthdate(), "Default birthdate should be null"); assertNull(user.getProvider(), "Default provider should be null"); - assertNull(user.getSocialId(), "Default socialId should be null"); } @Test @@ -35,7 +34,6 @@ class UserTest { Role role = Role.REGISTERED_USER; LocalDate birthdate = LocalDate.of(2000, 1, 1); String provider = "GOOGLE"; - String socialId = "social123"; user.setId(id); user.setEmail(email); @@ -45,7 +43,6 @@ class UserTest { user.setRole(role); user.setBirthdate(birthdate); user.setProvider(provider); - user.setSocialId(socialId); assertEquals(id, user.getId()); assertEquals(email, user.getEmail()); @@ -55,7 +52,6 @@ class UserTest { assertEquals(role, user.getRole()); assertEquals(birthdate, user.getBirthdate()); assertEquals(provider, user.getProvider()); - assertEquals(socialId, user.getSocialId()); } @Test @@ -67,7 +63,6 @@ class UserTest { Role role = Role.MODERATOR; LocalDate birthdate = LocalDate.of(1995, 5, 15); String provider = "EMAIL"; - String socialId = null; User user = new User(); user.setEmail(email); @@ -77,7 +72,6 @@ class UserTest { user.setRole(role); user.setBirthdate(birthdate); user.setProvider(provider); - user.setSocialId(socialId); // id remains null until set (by the persistence layer) @@ -89,7 +83,6 @@ class UserTest { assertEquals(role, user.getRole()); assertEquals(birthdate, user.getBirthdate()); assertEquals(provider, user.getProvider()); - assertNull(user.getSocialId(), "SocialId should be null"); } @Test diff --git a/src/test/java/com/safetypin/authentication/seeder/DevDataSeederTest.java b/src/test/java/com/safetypin/authentication/seeder/DevDataSeederTest.java index 1df2e3a1bdba993fb6f828b37938977bec1f861d..33caa25146cd47b6cf90e9390774cae75f7c6c83 100644 --- a/src/test/java/com/safetypin/authentication/seeder/DevDataSeederTest.java +++ b/src/test/java/com/safetypin/authentication/seeder/DevDataSeederTest.java @@ -51,7 +51,6 @@ class DevDataSeederTest { user.setRole(Role.MODERATOR); user.setBirthdate(LocalDate.of(1990, 1, 1)); user.setProvider("EMAIL"); - user.setSocialId("social_9999"); userRepository.save(user); long countBefore = userRepository.count(); diff --git a/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java b/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java index fe61c0e89fe629324ec3b48d980f31ed9c6f6d21..67358617ca0adcb807c47c55c196c98b43e77452 100644 --- a/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java +++ b/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java @@ -1,32 +1,18 @@ package com.safetypin.authentication.service; import com.safetypin.authentication.dto.RegistrationRequest; -import com.safetypin.authentication.dto.SocialLoginRequest; -import com.safetypin.authentication.dto.UserResponse; import com.safetypin.authentication.exception.InvalidCredentialsException; import com.safetypin.authentication.exception.UserAlreadyExistsException; import com.safetypin.authentication.model.Role; import com.safetypin.authentication.model.User; -import com.safetypin.authentication.repository.UserRepository; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import io.jsonwebtoken.security.Keys; -import jakarta.annotation.PostConstruct; -import org.junit.jupiter.api.BeforeAll; 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.boot.test.context.SpringBootTest; import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.test.context.TestPropertySource; -import java.security.Key; import java.time.LocalDate; -import java.util.Date; import java.util.Optional; import java.util.UUID; @@ -38,7 +24,7 @@ import static org.mockito.Mockito.*; class AuthenticationServiceTest { @Mock - private UserRepository userRepository; + private UserService userService; @Mock private PasswordEncoder passwordEncoder; @@ -46,18 +32,25 @@ class AuthenticationServiceTest { @Mock private OTPService otpService; - @InjectMocks + @Mock + private JwtService jwtService; + private AuthenticationService authenticationService; + @BeforeEach + void setUp() { + authenticationService = new AuthenticationService(userService, passwordEncoder, otpService, jwtService); + } + // registerUser tests - + @Test void testRegisterUser_UnderAge() { RegistrationRequest request = new RegistrationRequest(); request.setEmail("test@example.com"); request.setPassword("password"); request.setName("Test User"); - // set birthdate to 17 years old + // set birthdate to 15 years old request.setBirthdate(LocalDate.now().minusYears(15)); Exception exception = assertThrows(IllegalArgumentException.class, () -> @@ -74,7 +67,8 @@ class AuthenticationServiceTest { request.setName("Test User"); request.setBirthdate(LocalDate.now().minusYears(20)); - when(userRepository.findByEmail("test@example.com")).thenReturn(new User()); + User existingUser = new User(); + when(userService.findByEmail("test@example.com")).thenReturn(Optional.of(existingUser)); Exception exception = assertThrows(UserAlreadyExistsException.class, () -> authenticationService.registerUser(request) @@ -90,8 +84,9 @@ class AuthenticationServiceTest { request.setName("Test User"); request.setBirthdate(LocalDate.now().minusYears(20)); - when(userRepository.findByEmail("test@example.com")).thenReturn(null); + when(userService.findByEmail("test@example.com")).thenReturn(Optional.empty()); when(passwordEncoder.encode("password")).thenReturn("encodedPassword"); + User savedUser = new User(); savedUser.setEmail("test@example.com"); savedUser.setPassword("encodedPassword"); @@ -100,165 +95,36 @@ class AuthenticationServiceTest { savedUser.setRole(Role.REGISTERED_USER); savedUser.setBirthdate(request.getBirthdate()); savedUser.setProvider("EMAIL"); - savedUser.setSocialId(null); UUID id = UUID.randomUUID(); savedUser.setId(id); - when(userRepository.save(any(User.class))).thenReturn(savedUser); - when(userRepository.findById(id)).thenReturn(Optional.of(savedUser)); - - String token = authenticationService.registerUser(request); - assertNotNull(token); - UserResponse userResponse = authenticationService.getUserFromJwtToken(token); - assertEquals("test@example.com", userResponse.getEmail()); - // OTPService should be invoked to generate OTP. - verify(otpService, times(1)).generateOTP("test@example.com"); - } - - // socialLogin tests - - @Test - void testSocialLogin_UnderAge() { - SocialLoginRequest request = new SocialLoginRequest(); - request.setEmail("social@example.com"); - request.setName("Social User"); - request.setBirthdate(LocalDate.now().minusYears(15)); - request.setProvider("GOOGLE"); - request.setSocialId("social123"); - request.setSocialToken("token"); - - Exception exception = assertThrows(IllegalArgumentException.class, () -> - authenticationService.socialLogin(request) - ); - assertEquals("User must be at least 16 years old", exception.getMessage()); - } - @Test - void testSocialLogin_DuplicateEmailWithEmailProvider() { - SocialLoginRequest request = new SocialLoginRequest(); - request.setEmail("social@example.com"); - request.setName("Social User"); - request.setBirthdate(LocalDate.now().minusYears(25)); - request.setProvider("APPLE"); - request.setSocialId("social123"); - request.setSocialToken("token"); - - User existingUser = new User(); - existingUser.setEmail("social@example.com"); - existingUser.setPassword("encodedPassword"); - existingUser.setName("Existing User"); - existingUser.setVerified(false); - existingUser.setRole(Role.REGISTERED_USER); - existingUser.setBirthdate(LocalDate.now().minusYears(30)); - existingUser.setProvider("EMAIL"); - existingUser.setSocialId(null); + when(userService.save(any(User.class))).thenReturn(savedUser); + when(jwtService.generateToken(id)).thenReturn("jwtToken"); - when(userRepository.findByEmail("social@example.com")).thenReturn(existingUser); - - Exception exception = assertThrows(UserAlreadyExistsException.class, () -> - authenticationService.socialLogin(request) - ); - assertTrue(exception.getMessage().contains("An account with this email exists")); - } - - @Test - void testSocialLogin_ExistingSocialUser() { - SocialLoginRequest request = new SocialLoginRequest(); - request.setEmail("social@example.com"); - request.setName("Social User"); - request.setBirthdate(LocalDate.now().minusYears(25)); - request.setProvider("GOOGLE"); - request.setSocialId("social123"); - request.setSocialToken("token"); - - User existingUser = new User(); - existingUser.setEmail("social@example.com"); - existingUser.setPassword(null); - existingUser.setName("Social User"); - existingUser.setVerified(true); - existingUser.setRole(Role.REGISTERED_USER); - existingUser.setBirthdate(LocalDate.now().minusYears(25)); - existingUser.setProvider("GOOGLE"); - existingUser.setSocialId("social123"); - UUID id = UUID.randomUUID(); - existingUser.setId(id); - - when(userRepository.findByEmail("social@example.com")).thenReturn(existingUser); - when(userRepository.findById(id)).thenReturn(Optional.of(existingUser)); - when(userRepository.save(any(User.class))).thenReturn(existingUser); - - String token = authenticationService.socialLogin(request); - assertNotNull(token); - UserResponse userResponse = authenticationService.getUserFromJwtToken(token); - - assertEquals("social@example.com", userResponse.getEmail()); - } - - @Test - void testSocialLogin_NewUser() { - SocialLoginRequest request = new SocialLoginRequest(); - request.setEmail("social@example.com"); - request.setName("Social User"); - request.setBirthdate(LocalDate.now().minusYears(25)); - request.setProvider("GOOGLE"); - request.setSocialId("social123"); - request.setSocialToken("token"); - - when(userRepository.findByEmail("social@example.com")).thenReturn(null); - User savedUser = new User(); - savedUser.setEmail("social@example.com"); - savedUser.setPassword(null); - savedUser.setName("Social User"); - savedUser.setVerified(true); - savedUser.setRole(Role.REGISTERED_USER); - savedUser.setBirthdate(request.getBirthdate()); - savedUser.setProvider("GOOGLE"); - savedUser.setSocialId("social123"); - - UUID id = UUID.randomUUID(); - savedUser.setId(id); - when(userRepository.save(any(User.class))).thenReturn(savedUser); - when(userRepository.findById(id)).thenReturn(Optional.of(savedUser)); + String token = authenticationService.registerUser(request); - String token = authenticationService.socialLogin(request); assertNotNull(token); - UserResponse userResponse = authenticationService.getUserFromJwtToken(token); - assertEquals("social@example.com", userResponse.getEmail()); + assertEquals("jwtToken", token); + verify(otpService, times(1)).generateOTP("test@example.com"); + verify(userService, times(1)).save(any(User.class)); } // loginUser tests @Test void testLoginUser_EmailNotFound() { - when(userRepository.findByEmail("notfound@example.com")).thenReturn(null); + when(userService.findByEmail("notfound@example.com")).thenReturn(Optional.empty()); + Exception exception = assertThrows(InvalidCredentialsException.class, () -> authenticationService.loginUser("notfound@example.com", "password") ); - assertTrue(exception.getMessage().contains("Invalid email")); - } - @Test - void testLoginUser_InvalidPassword_NullPassword() { - User user = new User(); - user.setEmail("test@example.com"); - user.setPassword(null); - user.setName("Test User"); - user.setVerified(true); - user.setRole(Role.REGISTERED_USER); - user.setBirthdate(LocalDate.now().minusYears(20)); - user.setProvider("EMAIL"); - user.setSocialId(null); - - when(userRepository.findByEmail("test@example.com")).thenReturn(user); - - Exception exception = assertThrows(InvalidCredentialsException.class, () -> - authenticationService.loginUser("test@example.com", "password") - ); - assertTrue(exception.getMessage().contains("Invalid password")); + assertEquals("Invalid email", exception.getMessage()); } @Test - void testLoginUser_InvalidPassword_WrongMatch() { + void testLoginUser_InvalidPassword() { User user = new User(); user.setEmail("test@example.com"); user.setPassword("encodedPassword"); @@ -267,15 +133,15 @@ class AuthenticationServiceTest { user.setRole(Role.REGISTERED_USER); user.setBirthdate(LocalDate.now().minusYears(20)); user.setProvider("EMAIL"); - user.setSocialId(null); - when(userRepository.findByEmail("test@example.com")).thenReturn(user); + when(userService.findByEmail("test@example.com")).thenReturn(Optional.of(user)); when(passwordEncoder.matches("wrongPassword", "encodedPassword")).thenReturn(false); Exception exception = assertThrows(InvalidCredentialsException.class, () -> authenticationService.loginUser("test@example.com", "wrongPassword") ); - assertTrue(exception.getMessage().contains("Invalid password")); + + assertEquals("Invalid password", exception.getMessage()); } @Test @@ -288,55 +154,18 @@ class AuthenticationServiceTest { user.setRole(Role.REGISTERED_USER); user.setBirthdate(LocalDate.now().minusYears(20)); user.setProvider("EMAIL"); - user.setSocialId(null); UUID id = UUID.randomUUID(); - user.setId(id - ); - when(userRepository.findByEmail("test@example.com")).thenReturn(user); + user.setId(id); + + when(userService.findByEmail("test@example.com")).thenReturn(Optional.of(user)); when(passwordEncoder.matches("password", "encodedPassword")).thenReturn(true); - when(userRepository.findById(id)).thenReturn(Optional.of(user)); + when(jwtService.generateToken(id)).thenReturn("jwtToken"); String token = authenticationService.loginUser("test@example.com", "password"); - assertNotNull(token); - UserResponse userResponse = authenticationService.getUserFromJwtToken(token); - assertEquals("test@example.com", userResponse.getEmail()); - } - - // loginSocial tests - - @Test - void testLoginSocial_UserNotFound() { - when(userRepository.findByEmail("notfound@example.com")).thenReturn(null); - Exception exception = assertThrows(InvalidCredentialsException.class, () -> - authenticationService.loginSocial("notfound@example.com") - ); - assertTrue(exception.getMessage().contains("Social login failed")); - } - @Test - void testLoginSocial_Success() { - User user = new User(); - user.setEmail("social@example.com"); - user.setPassword(null); - user.setName("Social User"); - user.setVerified(true); - user.setRole(Role.REGISTERED_USER); - user.setBirthdate(LocalDate.now().minusYears(25)); - user.setProvider("GOOGLE"); - user.setSocialId("social123"); - - UUID id = UUID.randomUUID(); - user.setId(id); - - when(userRepository.findByEmail("social@example.com")).thenReturn(user); - when(userRepository.findById(id)).thenReturn(Optional.of(user)); - - String token = authenticationService.loginSocial("social@example.com"); assertNotNull(token); - UserResponse userResponse = authenticationService.getUserFromJwtToken(token); - assertEquals("social@example.com", userResponse.getEmail()); - + assertEquals("jwtToken", token); } // verifyOTP tests @@ -345,6 +174,7 @@ class AuthenticationServiceTest { void testVerifyOTP_Success() { // OTPService returns true and user is found when(otpService.verifyOTP("test@example.com", "123456")).thenReturn(true); + User user = new User(); user.setEmail("test@example.com"); user.setPassword("encodedPassword"); @@ -353,34 +183,37 @@ class AuthenticationServiceTest { user.setRole(Role.REGISTERED_USER); user.setBirthdate(LocalDate.now().minusYears(20)); user.setProvider("EMAIL"); - user.setSocialId(null); - when(userRepository.findByEmail("test@example.com")).thenReturn(user); - when(userRepository.save(any(User.class))).thenReturn(user); + when(userService.findByEmail("test@example.com")).thenReturn(Optional.of(user)); + when(userService.save(any(User.class))).thenReturn(user); boolean result = authenticationService.verifyOTP("test@example.com", "123456"); + assertTrue(result); assertTrue(user.isVerified()); - verify(userRepository, times(1)).save(user); + verify(userService, times(1)).save(user); } @Test void testVerifyOTP_Success_UserNotFound() { // OTPService returns true but user is not found when(otpService.verifyOTP("nonexistent@example.com", "123456")).thenReturn(true); - when(userRepository.findByEmail("nonexistent@example.com")).thenReturn(null); + when(userService.findByEmail("nonexistent@example.com")).thenReturn(Optional.empty()); boolean result = authenticationService.verifyOTP("nonexistent@example.com", "123456"); + assertTrue(result); - verify(userRepository, never()).save(any(User.class)); + verify(userService, never()).save(any(User.class)); } @Test void testVerifyOTP_Failure() { when(otpService.verifyOTP("test@example.com", "000000")).thenReturn(false); + boolean result = authenticationService.verifyOTP("test@example.com", "000000"); + assertFalse(result); - verify(userRepository, never()).save(any(User.class)); + verify(userService, never()).save(any(User.class)); } // forgotPassword tests @@ -395,23 +228,25 @@ class AuthenticationServiceTest { user.setRole(Role.REGISTERED_USER); user.setBirthdate(LocalDate.now().minusYears(20)); user.setProvider("EMAIL"); - user.setSocialId(null); - when(userRepository.findByEmail("test@example.com")).thenReturn(user); + when(userService.findByEmail("test@example.com")).thenReturn(Optional.of(user)); assertDoesNotThrow(() -> authenticationService.forgotPassword("test@example.com")); } @Test - void testForgotPassword_Invalid() { - // Case 1: user not found - when(userRepository.findByEmail("notfound@example.com")).thenReturn(null); - Exception exception1 = assertThrows(IllegalArgumentException.class, () -> + void testForgotPassword_UserNotFound() { + when(userService.findByEmail("notfound@example.com")).thenReturn(Optional.empty()); + + Exception exception = assertThrows(IllegalArgumentException.class, () -> authenticationService.forgotPassword("notfound@example.com") ); - assertTrue(exception1.getMessage().contains("Password reset is only available for email-registered users.")); - // Case 2: user exists but provider is not EMAIL + assertTrue(exception.getMessage().contains("Password reset is only available for email-registered users")); + } + + @Test + void testForgotPassword_NonEmailProvider() { User user = new User(); user.setEmail("social@example.com"); user.setPassword(null); @@ -420,152 +255,15 @@ class AuthenticationServiceTest { user.setRole(Role.REGISTERED_USER); user.setBirthdate(LocalDate.now().minusYears(25)); user.setProvider("GOOGLE"); - user.setSocialId("social123"); - - when(userRepository.findByEmail("social@example.com")).thenReturn(user); - Exception exception2 = assertThrows(IllegalArgumentException.class, () -> - authenticationService.forgotPassword("social@example.com") - ); - assertTrue(exception2.getMessage().contains("Password reset is only available for email-registered users.")); - } - - // postContent tests - - @Test - void testPostContent_UserNotFound() { - when(userRepository.findByEmail("notfound@example.com")).thenReturn(null); - String response = authenticationService.postContent("notfound@example.com", "Content"); - assertEquals("User not found. Please register.", response); - } - - @Test - void testPostContent_UserNotVerified() { - User user = new User(); - user.setEmail("test@example.com"); - user.setPassword("encodedPassword"); - user.setName("Test User"); - user.setVerified(false); - user.setRole(Role.REGISTERED_USER); - user.setBirthdate(LocalDate.now().minusYears(20)); - user.setProvider("EMAIL"); - user.setSocialId(null); - - when(userRepository.findByEmail("test@example.com")).thenReturn(user); - String response = authenticationService.postContent("test@example.com", "Content"); - assertTrue(response.contains("not verified")); - } - - @Test - void testPostContent_UserVerified() { - User user = new User(); - user.setEmail("test@example.com"); - user.setPassword("encodedPassword"); - user.setName("Test User"); - user.setVerified(true); - user.setRole(Role.REGISTERED_USER); - user.setBirthdate(LocalDate.now().minusYears(20)); - user.setProvider("EMAIL"); - user.setSocialId(null); - when(userRepository.findByEmail("test@example.com")).thenReturn(user); - String response = authenticationService.postContent("test@example.com", "Content"); - assertEquals("Content posted successfully", response); - } + when(userService.findByEmail("social@example.com")).thenReturn(Optional.of(user)); - @Test - void testJwtTokenExpirationTime() { - // Generate a token for a random UUID - UUID userId = UUID.randomUUID(); - String token = authenticationService.generateJwtToken(userId); - assertNotNull(token); - - // Parse the token to extract claims - Key key = Keys.hmacShaKeyFor("5047c55bfe120155fd4e884845682bb8b8815c0048a686cc664d1ea6c8e094da".getBytes()); - Claims claims = Jwts.parserBuilder() - .setSigningKey(key) - .build() - .parseClaimsJws(token) - .getBody(); - - // Extract the issued at and expiration times - Date issuedAt = claims.getIssuedAt(); - Date expiration = claims.getExpiration(); - assertNotNull(issuedAt); - assertNotNull(expiration); - - // Calculate difference and verify it equals 86400000L (24 hours) - long timeDifference = expiration.getTime() - issuedAt.getTime(); - assertEquals(86400000L, timeDifference, - "JWT token should expire exactly 24 hours (86400000 milliseconds) after issuance"); - } - - // Tests for getUserFromJwtToken method - - @Test - void testGetUserFromJwtToken_InvalidToken() { - // Test with malformed token - Exception exception = assertThrows(InvalidCredentialsException.class, () -> - authenticationService.getUserFromJwtToken("invalid.token.format") - ); - assertEquals("Invalid token", exception.getMessage()); - } - - @Test - void testGetUserFromJwtToken_ExpiredToken() throws Exception { - // Create a JWT token with custom expiration (already expired) - UUID userId = UUID.randomUUID(); - Key key = Keys.hmacShaKeyFor("5047c55bfe120155fd4e884845682bb8b8815c0048a686cc664d1ea6c8e094da".getBytes()); - - String expiredToken = Jwts.builder() - .setSubject(userId.toString()) - .setIssuedAt(new Date(System.currentTimeMillis() - 200000)) // 200 seconds ago - .setExpiration(new Date(System.currentTimeMillis() - 100000)) // 100 seconds ago (expired) - .signWith(key, SignatureAlgorithm.HS256) - .compact(); - - Exception exception = assertThrows(InvalidCredentialsException.class, () -> - authenticationService.getUserFromJwtToken(expiredToken) - ); - assertEquals("Invalid token", exception.getMessage()); - } - - @Test - void testGetUserFromJwtToken_UserNotFound() { - // Create valid token with random UUID that doesn't exist in DB - UUID nonExistentId = UUID.randomUUID(); - String token = authenticationService.generateJwtToken(nonExistentId); - - when(userRepository.findById(nonExistentId)).thenReturn(Optional.empty()); - - Exception exception = assertThrows(InvalidCredentialsException.class, () -> - authenticationService.getUserFromJwtToken(token) + Exception exception = assertThrows(IllegalArgumentException.class, () -> + authenticationService.forgotPassword("social@example.com") ); - assertEquals("User not found", exception.getMessage()); - } - - @Test - void testGetUserFromJwtToken_Success() { - // Create user and token for explicit testing - UUID userId = UUID.randomUUID(); - User user = new User(); - user.setId(userId); - user.setEmail("test@example.com"); - user.setName("Test User"); - user.setVerified(true); - user.setRole(Role.REGISTERED_USER); - - // Generate a valid token - String token = authenticationService.generateJwtToken(userId); - - when(userRepository.findById(userId)).thenReturn(Optional.of(user)); - - // Test successful retrieval - UserResponse response = authenticationService.getUserFromJwtToken(token); - assertNotNull(response); - assertEquals("test@example.com", response.getEmail()); - assertEquals("Test User", response.getName()); - assertTrue(response.isVerified()); + assertTrue(exception.getMessage().contains("Password reset is only available for email-registered users")); } -} + // Add missing methods for other functionality if needed +} \ No newline at end of file diff --git a/src/test/java/com/safetypin/authentication/service/GoogleAuthServiceTest.java b/src/test/java/com/safetypin/authentication/service/GoogleAuthServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..a411399dd66ca0d35fcdc0818a7b16f5048dc95a --- /dev/null +++ b/src/test/java/com/safetypin/authentication/service/GoogleAuthServiceTest.java @@ -0,0 +1,469 @@ +package com.safetypin.authentication.service; + +import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeTokenRequest; +import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken; +import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier; +import com.google.api.client.googleapis.auth.oauth2.GoogleTokenResponse; +import com.safetypin.authentication.dto.GoogleAuthDTO; +import com.safetypin.authentication.exception.ApiException; +import com.safetypin.authentication.exception.InvalidCredentialsException; +import com.safetypin.authentication.exception.UserAlreadyExistsException; +import com.safetypin.authentication.model.User; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.*; +import org.mockito.junit.jupiter.MockitoExtension; +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.Appender; +import org.slf4j.LoggerFactory; +import org.springframework.test.util.ReflectionTestUtils; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.*; +import java.time.LocalDate; +import java.util.Optional; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class GoogleAuthServiceTest { + + @Mock + private UserService userService; + + @Mock + private JwtService jwtService; + + @Mock + private GoogleIdToken idToken; + + @Mock + private GoogleIdToken.Payload payload; + + @Mock + private GoogleIdTokenVerifier verifier; + + @Mock + private GoogleAuthorizationCodeTokenRequest tokenRequest; + + @Mock + private GoogleTokenResponse tokenResponse; + + @Spy + @InjectMocks + private GoogleAuthService googleAuthService; + + @Mock + private Appender<ILoggingEvent> mockAppender; + + @Captor + private ArgumentCaptor<ILoggingEvent> loggingEventCaptor; + + private GoogleAuthDTO googleAuthDTO; + private UUID testUserId; + + private final String testAccessToken = "test-access-token"; + + private final String testGoogleClientId = "test-client-id"; + + private final String testIdToken = "test-id-token"; + + @BeforeEach + void setup() { + ReflectionTestUtils.setField(googleAuthService, "googleClientId", testGoogleClientId); + ReflectionTestUtils.setField(googleAuthService, "googleClientSecret", "test-client-secret"); + + googleAuthDTO = new GoogleAuthDTO(); + googleAuthDTO.setIdToken(testIdToken); + googleAuthDTO.setServerAuthCode("test-auth-code"); + + testUserId = UUID.randomUUID(); + // Get Logback Logger + Logger root = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + root.setLevel(Level.INFO); + + // Clear previous appenders to avoid duplicates + root.detachAndStopAllAppenders(); + + // Add mock appender + root.addAppender(mockAppender); + } + + private void setPrivateField(Object instance, String fieldName, Object value) throws Exception { + java.lang.reflect.Field field = instance.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + field.set(instance, value); + } + + @Test + void authenticate_NewUser_Success() throws Exception { + // Mock verify ID token + doReturn(payload).when(googleAuthService).verifyIdToken(anyString()); + when(payload.getEmail()).thenReturn("test@example.com"); + when(payload.get("name")).thenReturn("Test User"); + + // Mock user service + when(userService.findByEmail(anyString())).thenReturn(Optional.empty()); + + // Mock getAccessToken + doReturn(testAccessToken).when(googleAuthService).getAccessToken(anyString()); + + // Mock getUserBirthdate to return a specific date + LocalDate birthdate = LocalDate.of(1990, 1, 1); + doReturn(birthdate).when(googleAuthService).getUserBirthdate(anyString()); + + // Mock user save + User savedUser = new User(); + savedUser.setId(testUserId); + when(userService.save(any(User.class))).thenReturn(savedUser); + + // Mock JWT generation + when(jwtService.generateToken(any(UUID.class))).thenReturn("test-jwt-token"); + + // Execute + String result = googleAuthService.authenticate(googleAuthDTO); + + // Verify + assertEquals("test-jwt-token", result); + verify(userService).findByEmail("test@example.com"); + verify(userService).save(any(User.class)); + verify(jwtService).generateToken(testUserId); + } + + @Test + void authenticate_ExistingGoogleUser_Success() throws Exception { + // Mock verify ID token + doReturn(payload).when(googleAuthService).verifyIdToken(anyString()); + when(payload.getEmail()).thenReturn("test@example.com"); + + // Mock existing user + User existingUser = new User(); + existingUser.setId(testUserId); + existingUser.setProvider("GOOGLE"); + when(userService.findByEmail("test@example.com")).thenReturn(Optional.of(existingUser)); + + // Mock JWT generation + when(jwtService.generateToken(any(UUID.class))).thenReturn("test-jwt-token"); + + // Execute + String result = googleAuthService.authenticate(googleAuthDTO); + + // Verify + assertEquals("test-jwt-token", result); + verify(userService).findByEmail("test@example.com"); + verify(userService, never()).save(any(User.class)); + verify(jwtService).generateToken(testUserId); + } + + @Test + void authenticate_ExistingUserWithDifferentProvider_ThrowsException() throws Exception { + // Mock verify ID token + doReturn(payload).when(googleAuthService).verifyIdToken(anyString()); + when(payload.getEmail()).thenReturn("test@example.com"); + + // Mock existing user with different provider + User existingUser = new User(); + existingUser.setProvider("EMAIL"); + when(userService.findByEmail("test@example.com")).thenReturn(Optional.of(existingUser)); + + // Execute and verify + UserAlreadyExistsException exception = assertThrows( + UserAlreadyExistsException.class, + () -> googleAuthService.authenticate(googleAuthDTO) + ); + + assertTrue(exception.getMessage().contains("Please sign in using EMAIL")); + } + + @Test + void verifyIdToken_NullToken_ThrowsException() { + // Execute and verify + IllegalArgumentException exception = assertThrows( + IllegalArgumentException.class, + () -> googleAuthService.verifyIdToken(null) + ); + + assertEquals("ID Token cannot be null", exception.getMessage()); + } + + @Test + void verifyIdToken_ValidToken_ReturnsPayload() throws Exception { + // Mock the verifier creation + doReturn(verifier).when(googleAuthService).createIdTokenVerifier(); + + // Set up the verifier to return our mock ID token + when(verifier.verify(testIdToken)).thenReturn(idToken); + when(idToken.getPayload()).thenReturn(payload); + + // Execute + GoogleIdToken.Payload result = googleAuthService.verifyIdToken(testIdToken); + + // Verify + assertSame(payload, result); + verify(verifier).verify(testIdToken); + } + + @Test + void verifyIdToken_InvalidToken_ThrowsException() throws Exception { + // Create a spy on the service to override the verifier creation + + // Mock the verifier creation + doReturn(verifier).when(googleAuthService).createIdTokenVerifier(); + + // Set up the verifier to return null (invalid token) + when(verifier.verify(anyString())).thenReturn(null); + + // Execute and verify + InvalidCredentialsException exception = assertThrows( + InvalidCredentialsException.class, + () -> googleAuthService.verifyIdToken(testIdToken) + ); + + assertEquals("Invalid ID Token", exception.getMessage()); + verify(verifier).verify(testIdToken); + } + + @Test + void getAccessToken_Success() throws Exception { + + // Mock the token request creation + doReturn(tokenRequest).when(googleAuthService).createAuthorizationCodeTokenRequest("test-auth-code"); + + // Set up the token request to return our mock token response + when(tokenRequest.execute()).thenReturn((tokenResponse)); + when(tokenResponse.getAccessToken()).thenReturn(testAccessToken); + + // Execute + String result = googleAuthService.getAccessToken("test-auth-code"); + + // Verify + assertEquals(testAccessToken, result); + verify(tokenRequest).execute(); + } + + @Test + void extractBirthday_ValidResponse_ReturnsBirthdate() { + String jsonResponse = "{" + + "\"birthdays\": [" + + " {" + + " \"date\": {" + + " \"year\": 1990," + + " \"month\": 1," + + " \"day\": 15" + + " }" + + " }" + + "]" + + "}"; + + LocalDate result = googleAuthService.extractBirthday(jsonResponse); + + assertEquals(LocalDate.of(1990, 1, 15), result); + } + + @Test + void extractBirthday_NoYearProvided_ReturnsCurrentYear() { + String jsonResponse = "{" + + "\"birthdays\": [" + + " {" + + " \"date\": {" + + " \"month\": 1," + + " \"day\": 15" + + " }" + + " }" + + "]" + + "}"; + + LocalDate result = googleAuthService.extractBirthday(jsonResponse); + + assertEquals(LocalDate.of(LocalDate.now().getYear(), 1, 15), result); + } + + @Test + void extractBirthday_NoBirthdayField_ReturnsNull() { + String jsonResponse = "{}"; + + LocalDate result = googleAuthService.extractBirthday(jsonResponse); + + assertNull(result); + } + + @Test + void extractBirthday_EmptyBirthdaysArray_ReturnsNull() { + String jsonResponse = "{\"birthdays\": []}"; + + LocalDate result = googleAuthService.extractBirthday(jsonResponse); + + assertNull(result); + } + + @Test + void extractBirthday_NoDateField_ReturnsNull() { + String jsonResponse = "{" + + "\"birthdays\": [" + + " {}" + + "]" + + "}"; + + LocalDate result = googleAuthService.extractBirthday(jsonResponse); + + assertNull(result); + } + + @Test + void getUserBirthdate_Success() { + // Setup private method mocking + doReturn("test-json-response").when(googleAuthService).fetchUserData(anyString()); + + // Mock extract birthday + LocalDate birthdate = LocalDate.of(1990, 1, 15); + doReturn(birthdate).when(googleAuthService).extractBirthday(anyString()); + + // Execute + LocalDate result = googleAuthService.getUserBirthdate(testAccessToken); + + // Verify + assertEquals(birthdate, result); + } + + @Test + void authenticate_Exception_ThrowsApiException() throws Exception { + // Mock verify ID token to throw exception + doThrow(new IOException("Test exception")).when(googleAuthService).verifyIdToken(anyString()); + + // Execute and verify + ApiException exception = assertThrows( + ApiException.class, + () -> googleAuthService.authenticate(googleAuthDTO) + ); + + assertEquals("Authentication failed", exception.getMessage()); + } + + @Test + void testCreateIdTokenVerifier_Configurations() throws Exception { + // Create a real instance of GoogleAuthService for this test + GoogleAuthService realService = new GoogleAuthService(userService, jwtService); + + // Use reflection to set the client ID for testing + setPrivateField(realService, "googleClientId", testGoogleClientId); + + // Call the createIdTokenVerifier method + GoogleIdTokenVerifier googleVerifier = realService.createIdTokenVerifier(); + + // Assertions to verify the verifier's configuration + assertNotNull(googleVerifier, "Verifier should not be null"); + } + + @Test + void testCreateTokenRequests_Configurations() throws Exception { + // Create a real instance of GoogleAuthService for this test + GoogleAuthService realService = new GoogleAuthService(userService, jwtService); + + // Use reflection to set the client ID for testing + setPrivateField(realService, "googleClientId", testGoogleClientId); + + assertNotNull(realService.createAuthorizationCodeTokenRequest("dumb-bunny")); + } + + @Test + void testFetchUserData_Successful() { + // Create a test GoogleAuthService with a protected method for URL creation + GoogleAuthService spyService = spy(new GoogleAuthService(userService, jwtService) { + @Override + protected URL createURL(String urlString) throws IOException { + URL mockUrl = mock(URL.class); + HttpURLConnection mockConn = mock(HttpURLConnection.class); + + // Prepare input stream + String testResponse = "test response data"; + InputStream inputStream = new ByteArrayInputStream(testResponse.getBytes()); + + // Mock URL and connection behaviors + when(mockUrl.openConnection()).thenReturn(mockConn); + when(mockConn.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK); + when(mockConn.getInputStream()).thenReturn(inputStream); + + return mockUrl; + } + }); + + // Execute and verify + String response = spyService.fetchUserData(testAccessToken); + assertEquals("test response data", response); + } + + @Test + void testFetchUserData_ApiError() { + // Arrange & Act + String result = googleAuthService.fetchUserData(testAccessToken); + + // Assert + assertNull(result); + + // Verify logging occurred + verify(mockAppender).doAppend(loggingEventCaptor.capture()); + ILoggingEvent loggingEvent = loggingEventCaptor.getValue(); + + assertEquals(Level.ERROR, loggingEvent.getLevel()); + assertTrue(loggingEvent.getFormattedMessage().contains("Error fetching data from Google API")); + } + + @Test + void testFetchUserData_InvalidAPIFormat() throws IOException { + // Arrange + doThrow(new MalformedURLException("Invalid URL format")) + .when(googleAuthService).createURL(anyString()); + + // Act + String result = googleAuthService.fetchUserData(testAccessToken); + + // Assert + assertNull(result); + + // Verify logging occurred + verify(mockAppender).doAppend(loggingEventCaptor.capture()); + ILoggingEvent loggingEvent = loggingEventCaptor.getValue(); + + assertEquals(Level.ERROR, loggingEvent.getLevel()); + assertTrue(loggingEvent.getFormattedMessage().contains("Invalid API URL")); + } + + @Test + void testFetchUserData_IOException() { + // Create a test GoogleAuthService with a protected method for URL creation + GoogleAuthService spyService = spy(new GoogleAuthService(userService, jwtService) { + @Override + protected URL createURL(String urlString) throws IOException { + URL mockUrl = mock(URL.class); + HttpURLConnection mockConn = mock(HttpURLConnection.class); + + // Mock URL and connection behaviors + when(mockUrl.openConnection()).thenReturn(mockConn); + when(mockConn.getResponseCode()).thenThrow(new IOException("Network error")); + + return mockUrl; + } + }); + + // Execute and verify + String response = spyService.fetchUserData(testAccessToken); + assertNull(response); + + // Verify logging occurred + verify(mockAppender).doAppend(loggingEventCaptor.capture()); + ILoggingEvent loggingEvent = loggingEventCaptor.getValue(); + + assertEquals(Level.ERROR, loggingEvent.getLevel()); + assertTrue(loggingEvent.getFormattedMessage().contains("IO error when fetching user data")); + } + + +} \ No newline at end of file diff --git a/src/test/java/com/safetypin/authentication/service/JwtServiceTest.java b/src/test/java/com/safetypin/authentication/service/JwtServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..cc11b7f2cc730642b0dfff0a48ec9660e4aecdaa --- /dev/null +++ b/src/test/java/com/safetypin/authentication/service/JwtServiceTest.java @@ -0,0 +1,151 @@ +package com.safetypin.authentication.service; + +import com.safetypin.authentication.dto.UserResponse; +import com.safetypin.authentication.exception.InvalidCredentialsException; +import com.safetypin.authentication.model.User; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.JwtException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Date; +import java.util.Optional; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class JwtServiceTest { + + @Mock + private UserService userService; + + private JwtService jwtService; + + private final String secretKey = "testSecretKeyWithAtLeast256BitsForHmacSha256Algorithm"; + private final UUID userId = UUID.randomUUID(); + private final User mockUser = mock(User.class); + private final UserResponse mockUserResponse = mock(UserResponse.class); + + @BeforeEach + void setUp() { + // Create JwtService instance with the mocked UserService and test secret key + jwtService = new JwtService(secretKey, userService); + } + + @Test + void constructor_shouldInitializeKeyAndUserService() { + // Verify constructor properly initializes the service + assertNotNull(jwtService); + } + + @Test + void generateToken_shouldCreateValidJwt() { + // Generate a token + String token = jwtService.generateToken(userId); + + // Verify token is not null or empty + assertNotNull(token); + assertFalse(token.isEmpty()); + + // Verify token can be parsed + Claims claims = jwtService.parseToken(token); + assertEquals(userId.toString(), claims.getSubject()); + assertFalse(claims.getExpiration().before(new Date())); + } + + @Test + void parseToken_shouldDecodeValidToken() { + // Generate a token + String token = jwtService.generateToken(userId); + + // Parse the token + Claims claims = jwtService.parseToken(token); + + // Verify claims + assertNotNull(claims); + assertEquals(userId.toString(), claims.getSubject()); + assertNotNull(claims.getIssuedAt()); + assertNotNull(claims.getExpiration()); + } + + @Test + void parseToken_shouldThrowExceptionForInvalidToken() { + // Invalid token + String invalidToken = "invalid.token.string"; + + // Verify exception is thrown + assertThrows(JwtException.class, () -> jwtService.parseToken(invalidToken)); + } + + @Test + void getUserFromJwtToken_shouldReturnUserForValidToken() throws InvalidCredentialsException { + // Set up mock responses + when(userService.findById(userId)).thenReturn(Optional.of(mockUser)); + when(mockUser.generateUserResponse()).thenReturn(mockUserResponse); + + // Generate a token + String token = jwtService.generateToken(userId); + + // Get user from token + UserResponse response = jwtService.getUserFromJwtToken(token); + + // Verify result + assertSame(mockUserResponse, response); + verify(userService).findById(userId); + verify(mockUser).generateUserResponse(); + } + + @Test + void getUserFromJwtToken_shouldThrowExceptionForExpiredToken() { + // Create a JwtService with a custom expiration time + JwtService shortExpirationJwtService = new JwtService(secretKey, userService); + + // Create a new token with an expiration date in the past + Date pastDate = new Date(System.currentTimeMillis() - 1000); // 1 second in the past + + // Use reflection to mock the parseToken method to return expired claims + JwtService spyService = spy(shortExpirationJwtService); + Claims expiredClaims = mock(Claims.class); + when(expiredClaims.getExpiration()).thenReturn(pastDate); + when(expiredClaims.getSubject()).thenReturn(userId.toString()); + doReturn(expiredClaims).when(spyService).parseToken(anyString()); + + // Verify exception is thrown + InvalidCredentialsException exception = assertThrows( + InvalidCredentialsException.class, + () -> spyService.getUserFromJwtToken("expired-token") + ); + assertEquals("Token expired", exception.getMessage()); + } + + @Test + void getUserFromJwtToken_shouldThrowExceptionWhenUserNotFound() { + // Set up mock to return empty optional + when(userService.findById(userId)).thenReturn(Optional.empty()); + + // Generate token + String token = jwtService.generateToken(userId); + + // Verify exception is thrown + InvalidCredentialsException exception = assertThrows( + InvalidCredentialsException.class, + () -> jwtService.getUserFromJwtToken(token) + ); + assertEquals("User not found", exception.getMessage()); + verify(userService).findById(userId); + } + + @Test + void getUserFromJwtToken_shouldHandleInvalidToken() { + // Invalid token + String invalidToken = "invalid.token.string"; + + // Verify exception is thrown + assertThrows(JwtException.class, () -> jwtService.getUserFromJwtToken(invalidToken)); + } +} \ No newline at end of file diff --git a/src/test/java/com/safetypin/authentication/service/TokenExpirationTest.java b/src/test/java/com/safetypin/authentication/service/TokenExpirationTest.java index 6fcd5a1a471e93a0980b12f55cc7cb2f3abcc9d1..0db448055a6d2c6b778ba0dd51478c9d08a32363 100644 --- a/src/test/java/com/safetypin/authentication/service/TokenExpirationTest.java +++ b/src/test/java/com/safetypin/authentication/service/TokenExpirationTest.java @@ -7,21 +7,17 @@ import com.safetypin.authentication.repository.UserRepository; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtException; import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import io.jsonwebtoken.security.Keys; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.springframework.security.crypto.password.PasswordEncoder; -import java.security.Key; import java.util.Date; import java.util.Optional; import java.util.UUID; import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; public class TokenExpirationTest { @@ -34,9 +30,16 @@ public class TokenExpirationTest { @Mock private PasswordEncoder passwordEncoder; + @Mock + private UserService userService; + @Mock private OTPService otpService; + @Mock + private JwtService jwtService; + + @BeforeEach void setUp() { MockitoAnnotations.openMocks(this); @@ -46,7 +49,7 @@ public class TokenExpirationTest { void testExpiredTokenThrowsCorrectException() { // Create a special test-only version of AuthenticationService TestAuthenticationService testService = new TestAuthenticationService( - userRepository, passwordEncoder, otpService); + userService, passwordEncoder, otpService, jwtService); // Create a UUID for our test UUID userId = UUID.randomUUID(); @@ -69,10 +72,11 @@ public class TokenExpirationTest { // This class extends AuthenticationService to allow us to test specific code paths private class TestAuthenticationService extends AuthenticationService { - public TestAuthenticationService(UserRepository userRepository, + public TestAuthenticationService(UserService userService, PasswordEncoder passwordEncoder, - OTPService otpService) { - super(userRepository, passwordEncoder, otpService); + OTPService otpService, + JwtService jwtService) { + super(userService, passwordEncoder, otpService, jwtService); } // This method simulates the token expiration check portion of getUserFromJwtToken diff --git a/src/test/java/com/safetypin/authentication/service/UserServiceTest.java b/src/test/java/com/safetypin/authentication/service/UserServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..64dd562813fecb7c3eee0d4af5171c065b866798 --- /dev/null +++ b/src/test/java/com/safetypin/authentication/service/UserServiceTest.java @@ -0,0 +1,125 @@ +package com.safetypin.authentication.service; + +import com.safetypin.authentication.model.User; +import com.safetypin.authentication.repository.UserRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Optional; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class UserServiceTest { + + @Mock + private UserRepository userRepository; + + private UserService userService; + + @BeforeEach + void setUp() { + userService = new UserService(userRepository); + } + + @Test + void testFindById_WhenUserExists_ReturnUser() { + // Arrange + UUID userId = UUID.randomUUID(); + User expectedUser = new User(); + expectedUser.setId(userId); + + when(userRepository.findById(userId)).thenReturn(Optional.of(expectedUser)); + + // Act + Optional<User> result = userService.findById(userId); + + // Assert + assertTrue(result.isPresent()); + assertEquals(expectedUser, result.get()); + verify(userRepository).findById(userId); + } + + @Test + void testFindById_WhenUserDoesNotExist_ReturnEmptyOptional() { + // Arrange + UUID userId = UUID.randomUUID(); + when(userRepository.findById(userId)).thenReturn(Optional.empty()); + + // Act + Optional<User> result = userService.findById(userId); + + // Assert + assertFalse(result.isPresent()); + verify(userRepository).findById(userId); + } + + @Test + void testFindByEmail_WhenUserExists_ReturnUser() { + // Arrange + String email = "test@example.com"; + User expectedUser = new User(); + expectedUser.setEmail(email); + + when(userRepository.findByEmail(email)).thenReturn(expectedUser); + + // Act + Optional<User> result = userService.findByEmail(email); + + // Assert + assertTrue(result.isPresent()); + assertEquals(expectedUser, result.get()); + verify(userRepository).findByEmail(email); + } + + @Test + void testFindByEmail_WhenUserDoesNotExist_ReturnEmptyOptional() { + // Arrange + String email = "nonexistent@example.com"; + when(userRepository.findByEmail(email)).thenReturn(null); + + // Act + Optional<User> result = userService.findByEmail(email); + + // Assert + assertFalse(result.isPresent()); + verify(userRepository).findByEmail(email); + } + + @Test + void testSave_ShouldCallRepositorySaveAndReturnUser() { + // Arrange + User userToSave = new User(); + userToSave.setEmail("test@example.com"); + + when(userRepository.save(any(User.class))).thenReturn(userToSave); + + // Act + User savedUser = userService.save(userToSave); + + // Assert + assertNotNull(savedUser); + assertEquals(userToSave, savedUser); + verify(userRepository).save(userToSave); + } + + @Test + void testConstructor_InitializesRepositoryCorrectly() { + // Arrange & Act + UserService service = new UserService(userRepository); + + // Assert + assertNotNull(service); + + // Verify that the repository is correctly initialized by calling a method + UUID randomId = UUID.randomUUID(); + service.findById(randomId); + verify(userRepository).findById(randomId); + } +} \ No newline at end of file