From 1a83c642c19868dd9c7155cc89049fc4d4777ba7 Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Wed, 26 Feb 2025 13:40:14 +0700 Subject: [PATCH 01/70] add: update dependencies in pom.xml and include new database and security configurations --- pom.xml | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 65 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 936334a..7399793 100644 --- a/pom.xml +++ b/pom.xml @@ -66,8 +66,8 @@ <!-- JUnit 5 for unit and regression testing --> <dependency> <groupId>org.junit.jupiter</groupId> - <artifactId>junit-jupiter-api</artifactId> - <version>5.7.1</version> + <artifactId>junit-jupiter</artifactId> + <version>5.11.2</version> <scope>test</scope> </dependency> @@ -79,11 +79,34 @@ <scope>test</scope> </dependency> + <dependency> + <groupId>org.postgresql</groupId> + <artifactId>postgresql</artifactId> + <scope>runtime</scope> + </dependency> + + <!-- REST-assured for integration testing --> <dependency> <groupId>io.rest-assured</groupId> <artifactId>rest-assured</artifactId> - <version>4.4.0</version> + <version>4.5.1</version> + <scope>test</scope> + <exclusions> + <exclusion> + <groupId>org.codehaus.groovy</groupId> + <artifactId>groovy</artifactId> + </exclusion> + <exclusion> + <groupId>org.codehaus.groovy</groupId> + <artifactId>groovy-xml</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>io.rest-assured</groupId> + <artifactId>json-schema-validator</artifactId> + <version>4.5.1</version> <scope>test</scope> </dependency> @@ -94,6 +117,45 @@ <version>2.5.4</version> </dependency> + <!-- Spring Data JPA --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-data-jpa</artifactId> + </dependency> + + <!-- Spring Security for Password Encoding --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-security</artifactId> + </dependency> + + <!-- H2 Database for development/testing --> + <dependency> + <groupId>com.h2database</groupId> + <artifactId>h2</artifactId> + <scope>runtime</scope> + </dependency> + + <dependency> + <groupId>org.springframework.security</groupId> + <artifactId>spring-security-test</artifactId> + <version>6.1.1</version> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-validation</artifactId> + </dependency> + + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-test</artifactId> + <scope>test</scope> + </dependency> + + + </dependencies> <build> -- GitLab From ee659a1725f0c2a63fc59f4647e3608070490fce Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Wed, 26 Feb 2025 13:41:46 +0700 Subject: [PATCH 02/70] [RED] Implement unit test for HelloController's sayHello method --- .../authentication/HelloControllerTest.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/test/java/com/safetypin/authentication/HelloControllerTest.java diff --git a/src/test/java/com/safetypin/authentication/HelloControllerTest.java b/src/test/java/com/safetypin/authentication/HelloControllerTest.java new file mode 100644 index 0000000..cb308d4 --- /dev/null +++ b/src/test/java/com/safetypin/authentication/HelloControllerTest.java @@ -0,0 +1,15 @@ +package com.safetypin.authentication; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class HelloControllerTest { + + private final HelloController helloController = new HelloController(); + + @Test + void testSayHelloReturnsHelloWorld() { + String greeting = helloController.sayHello(); + assertEquals("Hello, World!", greeting); + } +} -- GitLab From 3dbe6c09c06137a5cecdd63ff1c67f4eb7360433 Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Wed, 26 Feb 2025 13:42:32 +0700 Subject: [PATCH 03/70] [REFACTOR] Update AuthenticationApplicationTests to verify main method execution --- .../authentication/AuthenticationApplicationTests.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/java/com/safetypin/authentication/AuthenticationApplicationTests.java b/src/test/java/com/safetypin/authentication/AuthenticationApplicationTests.java index 599099f..a528070 100644 --- a/src/test/java/com/safetypin/authentication/AuthenticationApplicationTests.java +++ b/src/test/java/com/safetypin/authentication/AuthenticationApplicationTests.java @@ -1,13 +1,13 @@ package com.safetypin.authentication; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -@SpringBootTest -class AuthenticationApplicationTests { +class AuthenticationApplicationTest { @Test - void contextLoads() { + void testMainDoesNotThrowException() { + // Calling the main method should load the context without throwing an exception. + assertDoesNotThrow(() -> AuthenticationApplication.main(new String[] {})); } - } -- GitLab From a81411a6f9aeffc5b6c1dc3e7e6c2c35ae503f12 Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Wed, 26 Feb 2025 13:42:53 +0700 Subject: [PATCH 04/70] [REFACTOR] Remove unnecessary blank line in AuthenticationApplication --- .../com/safetypin/authentication/AuthenticationApplication.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/safetypin/authentication/AuthenticationApplication.java b/src/main/java/com/safetypin/authentication/AuthenticationApplication.java index 9b1e951..b54a9d5 100644 --- a/src/main/java/com/safetypin/authentication/AuthenticationApplication.java +++ b/src/main/java/com/safetypin/authentication/AuthenticationApplication.java @@ -8,7 +8,6 @@ import org.springframework.web.bind.annotation.RestController; @SpringBootApplication public class AuthenticationApplication { - public static void main(String[] args) { SpringApplication.run(AuthenticationApplication.class, args); } -- GitLab From aef3175cdac1501b12e39ad1e05acef2c6853d1a Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Wed, 26 Feb 2025 13:43:19 +0700 Subject: [PATCH 05/70] [RED] Implement unit tests for AuthenticationController methods --- .../AuthenticationControllerTest.java | 187 ++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java diff --git a/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java b/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java new file mode 100644 index 0000000..9d335f4 --- /dev/null +++ b/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java @@ -0,0 +1,187 @@ +package com.safetypin.authentication.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.safetypin.authentication.dto.PasswordResetRequest; +import com.safetypin.authentication.dto.RegistrationRequest; +import com.safetypin.authentication.dto.SocialLoginRequest; +import com.safetypin.authentication.model.User; +import com.safetypin.authentication.service.AuthenticationService; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.http.MediaType; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.test.web.servlet.MockMvc; + +import java.time.LocalDate; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@WebMvcTest(AuthenticationController.class) +@Import({AuthenticationControllerTest.TestConfig.class, AuthenticationControllerTest.TestSecurityConfig.class}) +public class AuthenticationControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private AuthenticationService authenticationService; + + @Autowired + private ObjectMapper objectMapper; + + @TestConfiguration + static class TestConfig { + @Bean + public AuthenticationService authenticationService() { + return Mockito.mock(AuthenticationService.class); + } + } + + @TestConfiguration + static class TestSecurityConfig { + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http.csrf(csrf -> csrf.disable()) + .authorizeHttpRequests(authz -> authz.anyRequest().permitAll()); + return http.build(); + } + } + + @Test + public void testRegisterEmail() throws Exception { + RegistrationRequest request = new RegistrationRequest(); + request.setEmail("email@example.com"); + request.setPassword("password"); + request.setName("Test User"); + request.setBirthdate(LocalDate.now().minusYears(20)); + + User user = new User("email@example.com", "encodedPassword", "Test User", false, "USER", + request.getBirthdate(), "EMAIL", null); + user.setId(1L); + Mockito.when(authenticationService.registerUser(any(RegistrationRequest.class))).thenReturn(user); + + mockMvc.perform(post("/api/auth/register-email") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(1L)) + .andExpect(jsonPath("$.email").value("email@example.com")); + } + + @Test + public 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("social@example.com", null, "Social User", true, "USER", + request.getBirthdate(), "GOOGLE", "social123"); + user.setId(2L); + Mockito.when(authenticationService.socialLogin(any(SocialLoginRequest.class))).thenReturn(user); + + mockMvc.perform(post("/api/auth/register-social") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(2L)) + .andExpect(jsonPath("$.email").value("social@example.com")); + } + + @Test + public void testLoginEmail() throws Exception { + User user = new User("email@example.com", "encodedPassword", "Test User", true, "USER", + LocalDate.now().minusYears(20), "EMAIL", null); + user.setId(1L); + Mockito.when(authenticationService.loginUser(eq("email@example.com"), eq("password"))).thenReturn(user); + + mockMvc.perform(post("/api/auth/login-email") + .param("email", "email@example.com") + .param("password", "password")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(1L)) + .andExpect(jsonPath("$.email").value("email@example.com")); + } + + @Test + public void testLoginSocial() throws Exception { + User user = new User("social@example.com", null, "Social User", true, "USER", + LocalDate.now().minusYears(25), "GOOGLE", "social123"); + user.setId(2L); + Mockito.when(authenticationService.loginSocial(eq("social@example.com"))).thenReturn(user); + + mockMvc.perform(post("/api/auth/login-social") + .param("email", "social@example.com")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(2L)) + .andExpect(jsonPath("$.email").value("social@example.com")); + } + + @Test + public void testVerifyOTP_Success() throws Exception { + Mockito.when(authenticationService.verifyOTP(eq("email@example.com"), eq("123456"))).thenReturn(true); + + mockMvc.perform(post("/api/auth/verify-otp") + .param("email", "email@example.com") + .param("otp", "123456")) + .andExpect(status().isOk()) + .andExpect(content().string("User verified successfully")); + } + + @Test + public void testVerifyOTP_Failure() throws Exception { + Mockito.when(authenticationService.verifyOTP(eq("email@example.com"), eq("000000"))).thenReturn(false); + + mockMvc.perform(post("/api/auth/verify-otp") + .param("email", "email@example.com") + .param("otp", "000000")) + .andExpect(status().isOk()) + .andExpect(content().string("OTP verification failed")); + } + + @Test + public void testForgotPassword() throws Exception { + PasswordResetRequest request = new PasswordResetRequest(); + request.setEmail("email@example.com"); + + Mockito.doNothing().when(authenticationService).forgotPassword("email@example.com"); + + mockMvc.perform(post("/api/auth/forgot-password") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(content().string("Password reset instructions have been sent to your email (simulated)")); + } + + @Test + public void testPostContent() throws Exception { + Mockito.when(authenticationService.postContent(eq("email@example.com"), eq("Test Content"))) + .thenReturn("Content posted successfully"); + + 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 + public void testDashboard() throws Exception { + mockMvc.perform(get("/api/auth/dashboard")) + .andExpect(status().isOk()) + .andExpect(content().string("{}")); + } +} -- GitLab From 8fd71bed8b4cc9f53eb66b36861f018e6610af92 Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Wed, 26 Feb 2025 13:44:24 +0700 Subject: [PATCH 06/70] [GREEN] Add AuthenticationController with user registration and login endpoints --- .../controller/AuthenticationController.java | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 src/main/java/com/safetypin/authentication/controller/AuthenticationController.java diff --git a/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java b/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java new file mode 100644 index 0000000..9b09ad8 --- /dev/null +++ b/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java @@ -0,0 +1,90 @@ +package com.safetypin.authentication.controller; + +import com.safetypin.authentication.dto.PasswordResetRequest; +import com.safetypin.authentication.dto.RegistrationRequest; +import com.safetypin.authentication.dto.SocialLoginRequest; +import com.safetypin.authentication.exception.InvalidCredentialsException; +import com.safetypin.authentication.model.User; +import com.safetypin.authentication.service.AuthenticationService; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import jakarta.validation.Valid; +import com.safetypin.authentication.dto.ErrorResponse; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/auth") +public class AuthenticationController { + + private final AuthenticationService authenticationService; + + public AuthenticationController(AuthenticationService authenticationService) { + this.authenticationService = authenticationService; + } + + + // Endpoint for email registration + @PostMapping("/register-email") + public User registerEmail(@Valid @RequestBody RegistrationRequest request) { + return authenticationService.registerUser(request); + } + + // Endpoint for social registration/login + @PostMapping("/register-social") + public User registerSocial(@Valid @RequestBody SocialLoginRequest request) { + return authenticationService.socialLogin(request); + } + + // OTP verification endpoint + @PostMapping("/verify-otp") + public String verifyOTP(@RequestParam String email, @RequestParam String otp) { + boolean verified = authenticationService.verifyOTP(email, otp); + return verified ? "User verified successfully" : "OTP verification failed"; + } + + + + // Endpoint for email login + @PostMapping("/login-email") + public ResponseEntity<?> loginEmail(@RequestParam String email, @RequestParam String password) { + try { + return ResponseEntity.ok(authenticationService.loginUser(email, password)); + } catch (InvalidCredentialsException e){ + //TODO + return ResponseEntity.status(HttpStatus.UNAUTHORIZED) + .body(e.getMessage()); + } + + } + + // Endpoint for social login + @PostMapping("/login-social") + public User loginSocial(@RequestParam String email) { + return authenticationService.loginSocial(email); + } + + + + + // Endpoint for forgot password (only for email users) + @PostMapping("/forgot-password") + public String forgotPassword(@Valid @RequestBody PasswordResetRequest request) { + authenticationService.forgotPassword(request.getEmail()); + return "Password reset instructions have been sent to your email (simulated)"; + } + + + + + // Endpoint simulating a content post that requires a verified account + @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() { + return "{}"; + } +} -- GitLab From dcad3b5b311999f2c2791e239482df96ce341a9b Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Wed, 26 Feb 2025 13:45:31 +0700 Subject: [PATCH 07/70] [RED] Add unit tests for UserRepository to verify email lookup functionality --- .../repository/UserRepositoryTest.java | 46 +++ .../service/AuthenticationServiceTest.java | 333 ++++++++++++++++++ 2 files changed, 379 insertions(+) create mode 100644 src/test/java/com/safetypin/authentication/repository/UserRepositoryTest.java create mode 100644 src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java diff --git a/src/test/java/com/safetypin/authentication/repository/UserRepositoryTest.java b/src/test/java/com/safetypin/authentication/repository/UserRepositoryTest.java new file mode 100644 index 0000000..fd7e4ff --- /dev/null +++ b/src/test/java/com/safetypin/authentication/repository/UserRepositoryTest.java @@ -0,0 +1,46 @@ +package com.safetypin.authentication.repository; + +import com.safetypin.authentication.model.User; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +import static org.junit.jupiter.api.Assertions.*; + +@DataJpaTest +class UserRepositoryTest { + + @Autowired + private UserRepository userRepository; + + @BeforeEach + void setUp() { + userRepository.deleteAll(); + + // Create and save a User entity + User user = new User(); + user.setEmail("test@example.com"); + user.setPassword("password"); + user.setName("Test User"); + user.setRole("USER"); + userRepository.save(user); + } + + @Test + void testFindByEmailWhenUserExists() { + // Retrieve the user by email + User foundUser = userRepository.findByEmail("test@example.com"); + assertNotNull(foundUser, "Expected to find a user with the given email"); + assertEquals("test@example.com", foundUser.getEmail()); + assertEquals("Test User", foundUser.getName()); + } + + @Test + void testFindByEmailWhenUserDoesNotExist() { + // Attempt to find a user that doesn't exist + User foundUser = userRepository.findByEmail("nonexistent@example.com"); + assertNull(foundUser, "Expected no user to be found for a non-existent email"); + } + +} diff --git a/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java b/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java new file mode 100644 index 0000000..7064385 --- /dev/null +++ b/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java @@ -0,0 +1,333 @@ +package com.safetypin.authentication.service; + +import com.safetypin.authentication.dto.RegistrationRequest; +import com.safetypin.authentication.dto.SocialLoginRequest; +import com.safetypin.authentication.exception.InvalidCredentialsException; +import com.safetypin.authentication.exception.UserAlreadyExistsException; +import com.safetypin.authentication.model.User; +import com.safetypin.authentication.repository.UserRepository; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.crypto.password.PasswordEncoder; + +import java.time.LocalDate; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +public class AuthenticationServiceTest { + + @Mock + private UserRepository userRepository; + + @Mock + private PasswordEncoder passwordEncoder; + + @Mock + private OTPService otpService; + + @InjectMocks + private AuthenticationService authenticationService; + + // registerUser tests + + @Test + public 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 + request.setBirthdate(LocalDate.now().minusYears(17)); + + Exception exception = assertThrows(IllegalArgumentException.class, () -> + authenticationService.registerUser(request) + ); + assertEquals("User must be at least 18 years old", exception.getMessage()); + } + + @Test + public void testRegisterUser_DuplicateEmail() { + RegistrationRequest request = new RegistrationRequest(); + request.setEmail("test@example.com"); + request.setPassword("password"); + request.setName("Test User"); + request.setBirthdate(LocalDate.now().minusYears(20)); + + when(userRepository.findByEmail("test@example.com")).thenReturn(new User()); + + Exception exception = assertThrows(UserAlreadyExistsException.class, () -> + authenticationService.registerUser(request) + ); + assertTrue(exception.getMessage().contains("User already exists with this email")); + } + + @Test + public void testRegisterUser_Success() { + RegistrationRequest request = new RegistrationRequest(); + request.setEmail("test@example.com"); + request.setPassword("password"); + request.setName("Test User"); + request.setBirthdate(LocalDate.now().minusYears(20)); + + when(userRepository.findByEmail("test@example.com")).thenReturn(null); + when(passwordEncoder.encode("password")).thenReturn("encodedPassword"); + User savedUser = new User("test@example.com", "encodedPassword", "Test User", false, "USER", + request.getBirthdate(), "EMAIL", null); + savedUser.setId(1L); + when(userRepository.save(any(User.class))).thenReturn(savedUser); + + User result = authenticationService.registerUser(request); + assertNotNull(result); + assertEquals("test@example.com", result.getEmail()); + // OTPService should be invoked to generate OTP. + verify(otpService, times(1)).generateOTP("test@example.com"); + } + + // socialLogin tests + + @Test + public void testSocialLogin_UnderAge() { + SocialLoginRequest request = new SocialLoginRequest(); + request.setEmail("social@example.com"); + request.setName("Social User"); + request.setBirthdate(LocalDate.now().minusYears(17)); + request.setProvider("GOOGLE"); + request.setSocialId("social123"); + request.setSocialToken("token"); + + Exception exception = assertThrows(IllegalArgumentException.class, () -> + authenticationService.socialLogin(request) + ); + assertEquals("User must be at least 18 years old", exception.getMessage()); + } + + @Test + public 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("social@example.com", "encodedPassword", "Existing User", false, "USER", + LocalDate.now().minusYears(30), "EMAIL", null); + when(userRepository.findByEmail("social@example.com")).thenReturn(existingUser); + + Exception exception = assertThrows(IllegalArgumentException.class, () -> + authenticationService.socialLogin(request) + ); + assertTrue(exception.getMessage().contains("An account with this email exists")); + } + + @Test + public 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("social@example.com", null, "Social User", true, "USER", + LocalDate.now().minusYears(25), "GOOGLE", "social123"); + when(userRepository.findByEmail("social@example.com")).thenReturn(existingUser); + + User result = authenticationService.socialLogin(request); + assertNotNull(result); + assertEquals("social@example.com", result.getEmail()); + } + + @Test + public 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("social@example.com", null, "Social User", true, "USER", + request.getBirthdate(), "GOOGLE", "social123"); + savedUser.setId(2L); + when(userRepository.save(any(User.class))).thenReturn(savedUser); + + User result = authenticationService.socialLogin(request); + assertNotNull(result); + assertEquals("social@example.com", result.getEmail()); + } + + // loginUser tests + + @Test + public void testLoginUser_EmailNotFound() { + when(userRepository.findByEmail("notfound@example.com")).thenReturn(null); + Exception exception = assertThrows(InvalidCredentialsException.class, () -> + authenticationService.loginUser("notfound@example.com", "password") + ); + assertTrue(exception.getMessage().contains("Invalid email")); + } + + @Test + public void testLoginUser_InvalidPassword_NullPassword() { + User user = new User("test@example.com", null, "Test User", true, "USER", + LocalDate.now().minusYears(20), "EMAIL", 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")); + } + + @Test + public void testLoginUser_InvalidPassword_WrongMatch() { + User user = new User("test@example.com", "encodedPassword", "Test User", true, "USER", + LocalDate.now().minusYears(20), "EMAIL", null); + when(userRepository.findByEmail("test@example.com")).thenReturn(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")); + } + + @Test + public void testLoginUser_Success() { + User user = new User("test@example.com", "encodedPassword", "Test User", true, "USER", + LocalDate.now().minusYears(20), "EMAIL", null); + when(userRepository.findByEmail("test@example.com")).thenReturn(user); + when(passwordEncoder.matches("password", "encodedPassword")).thenReturn(true); + + User result = authenticationService.loginUser("test@example.com", "password"); + assertNotNull(result); + assertEquals("test@example.com", result.getEmail()); + } + + // loginSocial tests + + @Test + public 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 + public void testLoginSocial_Success() { + User user = new User("social@example.com", null, "Social User", true, "USER", + LocalDate.now().minusYears(25), "GOOGLE", "social123"); + when(userRepository.findByEmail("social@example.com")).thenReturn(user); + + User result = authenticationService.loginSocial("social@example.com"); + assertNotNull(result); + assertEquals("social@example.com", result.getEmail()); + } + + // verifyOTP tests + + @Test + public void testVerifyOTP_Success() { + // OTPService returns true and user is found + when(otpService.verifyOTP("test@example.com", "123456")).thenReturn(true); + User user = new User("test@example.com", "encodedPassword", "Test User", false, "USER", + LocalDate.now().minusYears(20), "EMAIL", null); + when(userRepository.findByEmail("test@example.com")).thenReturn(user); + when(userRepository.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); + } + + @Test + public 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); + + boolean result = authenticationService.verifyOTP("nonexistent@example.com", "123456"); + assertTrue(result); + verify(userRepository, never()).save(any(User.class)); + } + + @Test + public 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)); + } + + // forgotPassword tests + + @Test + public void testForgotPassword_Success() { + User user = new User("test@example.com", "encodedPassword", "Test User", true, "USER", + LocalDate.now().minusYears(20), "EMAIL", null); + when(userRepository.findByEmail("test@example.com")).thenReturn(user); + + assertDoesNotThrow(() -> authenticationService.forgotPassword("test@example.com")); + } + + @Test + public void testForgotPassword_Invalid() { + // Case 1: user not found + when(userRepository.findByEmail("notfound@example.com")).thenReturn(null); + Exception exception1 = 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 + User user = new User("social@example.com", null, "Social User", true, "USER", + LocalDate.now().minusYears(25), "GOOGLE", "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 + public 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 + public void testPostContent_UserNotVerified() { + User user = new User("test@example.com", "encodedPassword", "Test User", false, "USER", + LocalDate.now().minusYears(20), "EMAIL", null); + when(userRepository.findByEmail("test@example.com")).thenReturn(user); + String response = authenticationService.postContent("test@example.com", "Content"); + assertTrue(response.contains("not verified")); + } + + @Test + public void testPostContent_UserVerified() { + User user = new User("test@example.com", "encodedPassword", "Test User", true, "USER", + LocalDate.now().minusYears(20), "EMAIL", null); + when(userRepository.findByEmail("test@example.com")).thenReturn(user); + String response = authenticationService.postContent("test@example.com", "Content"); + assertEquals("Content posted successfully", response); + } +} -- GitLab From 83c65b8ee866158a5df8aa586ddaf8994adcc639 Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Wed, 26 Feb 2025 13:45:41 +0700 Subject: [PATCH 08/70] [RED] Add unit tests for OTPService to validate OTP generation and verification --- .../service/OTPServiceTest.java | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 src/test/java/com/safetypin/authentication/service/OTPServiceTest.java diff --git a/src/test/java/com/safetypin/authentication/service/OTPServiceTest.java b/src/test/java/com/safetypin/authentication/service/OTPServiceTest.java new file mode 100644 index 0000000..cfca094 --- /dev/null +++ b/src/test/java/com/safetypin/authentication/service/OTPServiceTest.java @@ -0,0 +1,80 @@ +package com.safetypin.authentication.service; + +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Constructor; +import java.time.LocalDateTime; +import java.util.concurrent.ConcurrentHashMap; + +import static org.junit.jupiter.api.Assertions.*; + +class OTPServiceTest { + + @Test + void testGenerateOTP() { + OTPService otpService = new OTPService(); + String email = "user@example.com"; + String otp = otpService.generateOTP(email); + assertNotNull(otp, "OTP should not be null"); + assertEquals(6, otp.length(), "OTP should be 6 characters long"); + assertTrue(otp.matches("\\d{6}"), "OTP should consist of 6 digits"); + } + + @Test + void testVerifyOTPSuccess() { + OTPService otpService = new OTPService(); + String email = "user@example.com"; + String otp = otpService.generateOTP(email); + // Immediately verify the generated OTP; it should succeed. + boolean result = otpService.verifyOTP(email, otp); + assertTrue(result, "The OTP should verify successfully"); + } + + @Test + void testVerifyOTPWrongOtp() { + OTPService otpService = new OTPService(); + String email = "user@example.com"; + otpService.generateOTP(email); + // Try verifying with an incorrect OTP. + boolean result = otpService.verifyOTP(email, "000000"); + assertFalse(result, "Verification should fail for an incorrect OTP"); + } + + @Test + void testVerifyOTPExpired() throws Exception { + OTPService otpService = new OTPService(); + String email = "user@example.com"; + String otp = otpService.generateOTP(email); + + // Access the private otpStorage field via reflection. + java.lang.reflect.Field otpStorageField = OTPService.class.getDeclaredField("otpStorage"); + otpStorageField.setAccessible(true); + @SuppressWarnings("unchecked") + ConcurrentHashMap<String, Object> otpStorage = (ConcurrentHashMap<String, Object>) otpStorageField.get(otpService); + + // Retrieve the current OTPDetails instance. + Object oldOtpDetails = otpStorage.get(email); + assertNotNull(oldOtpDetails, "OTPDetails instance should exist"); + + // Use reflection to get the private constructor of OTPDetails. + Class<?> otpDetailsClass = oldOtpDetails.getClass(); + Constructor<?> constructor = otpDetailsClass.getDeclaredConstructor(String.class, LocalDateTime.class); + constructor.setAccessible(true); + // Create a new OTPDetails instance with an expired time (3 minutes ago). + Object expiredOtpDetails = constructor.newInstance(otp, LocalDateTime.now().minusMinutes(3)); + // Replace the old OTPDetails with the expired one. + otpStorage.put(email, expiredOtpDetails); + + // Now verification should fail because the OTP is expired. + boolean result = otpService.verifyOTP(email, otp); + assertFalse(result, "The OTP should be expired and verification should fail"); + } + + @Test + void testVerifyOTPWhenNotGenerated() { + OTPService otpService = new OTPService(); + // No OTP was generated for this email, so verification should return false. + boolean result = otpService.verifyOTP("nonexistent@example.com", "123456"); + assertFalse(result, "Verification should fail when no OTP is generated for the given email"); + } +} -- GitLab From 97e905194c99d76d2a16af3a09c9fea65c4af0ba Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Wed, 26 Feb 2025 13:48:01 +0700 Subject: [PATCH 09/70] [GREEN] Add UserRepository interface for user data access with email lookup --- .../authentication/repository/UserRepository.java | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/main/java/com/safetypin/authentication/repository/UserRepository.java diff --git a/src/main/java/com/safetypin/authentication/repository/UserRepository.java b/src/main/java/com/safetypin/authentication/repository/UserRepository.java new file mode 100644 index 0000000..bef7fa9 --- /dev/null +++ b/src/main/java/com/safetypin/authentication/repository/UserRepository.java @@ -0,0 +1,10 @@ +package com.safetypin.authentication.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import com.safetypin.authentication.model.User; + +@Repository +public interface UserRepository extends JpaRepository<User, Long> { + User findByEmail(String email); +} -- GitLab From 953b1f5fc3ddfdbc77d7b4d5604a7660ffd9bd3c Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Wed, 26 Feb 2025 13:48:25 +0700 Subject: [PATCH 10/70] [GREEN] Add OTPService for generating and verifying one-time passwords --- .../authentication/service/OTPService.java | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/main/java/com/safetypin/authentication/service/OTPService.java diff --git a/src/main/java/com/safetypin/authentication/service/OTPService.java b/src/main/java/com/safetypin/authentication/service/OTPService.java new file mode 100644 index 0000000..ac9ee7d --- /dev/null +++ b/src/main/java/com/safetypin/authentication/service/OTPService.java @@ -0,0 +1,46 @@ +package com.safetypin.authentication.service; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import java.time.LocalDateTime; +import java.util.Random; +import java.util.concurrent.ConcurrentHashMap; + +@Service +public class OTPService { + + private static final long OTP_EXPIRATION_SECONDS = 120; // 2 minutes expiration + private static final Logger log = LoggerFactory.getLogger(OTPService.class); + private final ConcurrentHashMap<String, OTPDetails> otpStorage = new ConcurrentHashMap<>(); + private final Random random = new Random(); + + public String generateOTP(String email) { + String otp = String.format("%06d", random.nextInt(1000000)); + OTPDetails details = new OTPDetails(otp, LocalDateTime.now()); + otpStorage.put(email, details); + // Simulate sending OTP via email (in production, integrate with an email service) + log.info("Sending OTP {} to {}", otp, email); + return otp; + } + + public boolean verifyOTP(String email, String otp) { + OTPDetails details = otpStorage.get(email); + if (details == null) { + return false; + } + // Check if OTP has expired + if (details.generatedAt().plusSeconds(OTP_EXPIRATION_SECONDS).isBefore(LocalDateTime.now())) { + otpStorage.remove(email); + return false; + } + if (details.otp().equals(otp)) { + otpStorage.remove(email); + return true; + } + return false; + } + + private record OTPDetails(String otp, LocalDateTime generatedAt) { + } +} -- GitLab From 40ad2141356a727cacbcc35678d2bf9f6eacc754 Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Wed, 26 Feb 2025 13:50:55 +0700 Subject: [PATCH 11/70] [GREEN] Add AuthenticationService for user registration, login, and OTP verification --- .../service/AuthenticationService.java | 135 ++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 src/main/java/com/safetypin/authentication/service/AuthenticationService.java diff --git a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java new file mode 100644 index 0000000..5b92625 --- /dev/null +++ b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java @@ -0,0 +1,135 @@ +package com.safetypin.authentication.service; + +import com.safetypin.authentication.dto.RegistrationRequest; +import com.safetypin.authentication.dto.SocialLoginRequest; +import com.safetypin.authentication.exception.InvalidCredentialsException; +import com.safetypin.authentication.exception.UserAlreadyExistsException; +import com.safetypin.authentication.model.User; +import com.safetypin.authentication.repository.UserRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import java.time.LocalDate; +import java.time.Period; + +@Service +public class AuthenticationService { + + private final UserRepository userRepository; + private final PasswordEncoder passwordEncoder; + private final OTPService otpService; + private static final Logger logger = LoggerFactory.getLogger(AuthenticationService.class); + + public AuthenticationService(UserRepository userRepository, PasswordEncoder passwordEncoder, OTPService otpService) { + this.userRepository = userRepository; + this.passwordEncoder = passwordEncoder; + this.otpService = otpService; + } + + // Registration using email – includes birthdate and OTP generation + public User registerUser(RegistrationRequest request) { + if (calculateAge(request.getBirthdate()) < 18) { + throw new IllegalArgumentException("User must be at least 18 years old"); + } + if (userRepository.findByEmail(request.getEmail()) != null) { + throw new UserAlreadyExistsException("User already exists with this email. If you registered using social login, please sign in with Google/Apple."); + } + String encodedPassword = passwordEncoder.encode(request.getPassword()); + User user = new User(request.getEmail(), encodedPassword, request.getName(), false, "USER", + request.getBirthdate(), "EMAIL", null); + user = userRepository.save(user); + otpService.generateOTP(request.getEmail()); + logger.info("OTP generated for {} at {}", request.getEmail(), java.time.LocalDateTime.now()); + return user; + } + + // Social registration/login – simulating data fetched from Google/Apple + public User socialLogin(SocialLoginRequest request) { + if (calculateAge(request.getBirthdate()) < 18) { + throw new IllegalArgumentException("User must be at least 18 years old"); + } + User existing = userRepository.findByEmail(request.getEmail()); + if (existing != null) { + if ("EMAIL".equals(existing.getProvider())) { + throw new IllegalArgumentException("An account with this email exists. Please sign in using your email and password."); + } + return existing; + } + User user = new User(request.getEmail(), null, request.getName(), true, "USER", + request.getBirthdate(), request.getProvider().toUpperCase(), request.getSocialId()); + user = userRepository.save(user); + logger.info("User registered via {}: {} at {}", request.getProvider(), request.getEmail(), java.time.LocalDateTime.now()); + return user; + } + + // Email login with detailed error messages + public User loginUser(String email, String rawPassword) { + User user = userRepository.findByEmail(email); + if (user == null) { + // email not exists + logger.warn("Login failed: Email not found for {}", email); + throw new InvalidCredentialsException("Invalid email"); + } + if (!passwordEncoder.matches(rawPassword, user.getPassword())) { + // incorrect password + logger.warn("Login failed: Incorrect password for {}", email); + throw new InvalidCredentialsException("Invalid password"); + } + logger.info("User logged in: {} at {}", email, java.time.LocalDateTime.now()); + return user; + } + + // Social login verification (assumed to be pre-verified externally) + public User 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: {} at {}", email, java.time.LocalDateTime.now()); + return user; + } + + // 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) { + user.setVerified(true); + userRepository.save(user); + logger.info("OTP verified for {} at {}", email, java.time.LocalDateTime.now()); + } + } else { + logger.warn("OTP verification failed for {} at {}", email, java.time.LocalDateTime.now()); + } + return result; + } + + // Forgot password – only applicable for email-registered users + public void forgotPassword(String email) { + User user = userRepository.findByEmail(email); + if (user == null || !"EMAIL".equals(user.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 for {} at {}", email, java.time.LocalDateTime.now()); + } + + // Example method representing posting content that requires a verified account + public String postContent(String email, String content) { + 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."; + } + // 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(); + } +} -- GitLab From f661a1e7ed118eddd334d72b0048cbffd494a1d5 Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Wed, 26 Feb 2025 13:51:08 +0700 Subject: [PATCH 12/70] [RED] Add unit tests for DTOs to validate request and error response structures --- .../safetypin/authentication/dto/DtoTest.java | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 src/test/java/com/safetypin/authentication/dto/DtoTest.java diff --git a/src/test/java/com/safetypin/authentication/dto/DtoTest.java b/src/test/java/com/safetypin/authentication/dto/DtoTest.java new file mode 100644 index 0000000..645db7e --- /dev/null +++ b/src/test/java/com/safetypin/authentication/dto/DtoTest.java @@ -0,0 +1,93 @@ +package com.safetypin.authentication.dto; + +import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import jakarta.validation.ValidatorFactory; +import jakarta.validation.ConstraintViolation; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Set; + +public class DtoTest { + + private final Validator validator; + + public DtoTest() { + ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); + this.validator = factory.getValidator(); + } + + @Test + void testErrorResponseConstructor() { + ErrorResponse errorResponse = new ErrorResponse(404, "Resource not found"); + + assertThat(errorResponse.getStatus()).isEqualTo(404); + assertThat(errorResponse.getMessage()).isEqualTo("Resource not found"); + assertThat(errorResponse.getTimestamp()).isNotNull(); + assertThat(errorResponse.getTimestamp()).isBeforeOrEqualTo(LocalDateTime.now()); + } + + @Test + void testPasswordResetRequestValid() { + PasswordResetRequest request = new PasswordResetRequest(); + request.setEmail("user@example.com"); + + Set<ConstraintViolation<PasswordResetRequest>> violations = validator.validate(request); + assertThat(violations).isEmpty(); + } + + @Test + void testPasswordResetRequestInvalidEmail() { + PasswordResetRequest request = new PasswordResetRequest(); + request.setEmail("invalid-email"); + + Set<ConstraintViolation<PasswordResetRequest>> violations = validator.validate(request); + assertThat(violations).isNotEmpty(); + } + + @Test + void testRegistrationRequestValid() { + RegistrationRequest request = new RegistrationRequest(); + request.setEmail("user@example.com"); + request.setPassword("securePassword"); + request.setName("John Doe"); + request.setBirthdate(LocalDate.of(1995, 5, 10)); + + Set<ConstraintViolation<RegistrationRequest>> violations = validator.validate(request); + assertThat(violations).isEmpty(); + } + + @Test + void testRegistrationRequestMissingFields() { + RegistrationRequest request = new RegistrationRequest(); // Missing required fields + + Set<ConstraintViolation<RegistrationRequest>> violations = validator.validate(request); + assertThat(violations).isNotEmpty(); + assertThat(violations).hasSize(4); // Email, password, name, and birthdate should all be invalid + } + + @Test + void testSocialLoginRequestValid() { + SocialLoginRequest request = new SocialLoginRequest(); + request.setProvider("GOOGLE"); + request.setSocialToken("validToken"); + request.setEmail("socialuser@example.com"); + request.setName("Social User"); + request.setBirthdate(LocalDate.of(2000, 1, 1)); + request.setSocialId("123456789"); + + Set<ConstraintViolation<SocialLoginRequest>> violations = validator.validate(request); + assertThat(violations).isEmpty(); + } + + @Test + void testSocialLoginRequestMissingFields() { + SocialLoginRequest request = new SocialLoginRequest(); // Missing required fields + + Set<ConstraintViolation<SocialLoginRequest>> violations = validator.validate(request); + assertThat(violations).isNotEmpty(); + assertThat(violations).hasSize(6); // All fields should be invalid + } +} -- GitLab From 3df49e016cebfbf0f7818efd09ed024b9103120e Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Wed, 26 Feb 2025 13:51:23 +0700 Subject: [PATCH 13/70] [GREEN] Add ErrorResponse DTO for standardized error handling in API responses --- .../authentication/dto/ErrorResponse.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/main/java/com/safetypin/authentication/dto/ErrorResponse.java diff --git a/src/main/java/com/safetypin/authentication/dto/ErrorResponse.java b/src/main/java/com/safetypin/authentication/dto/ErrorResponse.java new file mode 100644 index 0000000..e7bb410 --- /dev/null +++ b/src/main/java/com/safetypin/authentication/dto/ErrorResponse.java @@ -0,0 +1,25 @@ +package com.safetypin.authentication.dto; + +import lombok.Data; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.time.LocalDateTime; + +@Data +@Getter +@Setter +@NoArgsConstructor +public class ErrorResponse{ + private int status; + private String message; + private LocalDateTime timestamp; + + public ErrorResponse(int status, String message) { + this.status = status; + this.message = message; + this.timestamp = LocalDateTime.now(); + } + +} \ No newline at end of file -- GitLab From 2beecbe70a606c0ef2a64c668ffa1629a4760368 Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Wed, 26 Feb 2025 13:51:31 +0700 Subject: [PATCH 14/70] [GREEN] Add PasswordResetRequest DTO for handling password reset requests --- .../dto/PasswordResetRequest.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/main/java/com/safetypin/authentication/dto/PasswordResetRequest.java diff --git a/src/main/java/com/safetypin/authentication/dto/PasswordResetRequest.java b/src/main/java/com/safetypin/authentication/dto/PasswordResetRequest.java new file mode 100644 index 0000000..9b12ed8 --- /dev/null +++ b/src/main/java/com/safetypin/authentication/dto/PasswordResetRequest.java @@ -0,0 +1,18 @@ +package com.safetypin.authentication.dto; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +public class PasswordResetRequest { + + @NotBlank + @Email + private String email; + + // Getters and setters + +} -- GitLab From 1c1a542a225b8283417c2e37a0d8d88ca3ab744a Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Wed, 26 Feb 2025 13:51:41 +0700 Subject: [PATCH 15/70] [GREEN] Add RegistrationRequest DTO for user registration data validation --- .../dto/RegistrationRequest.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/main/java/com/safetypin/authentication/dto/RegistrationRequest.java diff --git a/src/main/java/com/safetypin/authentication/dto/RegistrationRequest.java b/src/main/java/com/safetypin/authentication/dto/RegistrationRequest.java new file mode 100644 index 0000000..ddb267f --- /dev/null +++ b/src/main/java/com/safetypin/authentication/dto/RegistrationRequest.java @@ -0,0 +1,30 @@ +package com.safetypin.authentication.dto; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDate; + +@Setter +@Getter +public class RegistrationRequest { + + @NotBlank + @Email + private String email; + + @NotBlank + private String password; + + @NotBlank + private String name; + + @NotNull + private LocalDate birthdate; + + // Getters and setters + +} -- GitLab From 242cf0a5040c7b11610438afdba2b08f5cd5b8ea Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Wed, 26 Feb 2025 13:51:54 +0700 Subject: [PATCH 16/70] [GREEN] Add SocialLoginRequest DTO and custom exceptions for user authentication --- .../dto/SocialLoginRequest.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/main/java/com/safetypin/authentication/dto/SocialLoginRequest.java diff --git a/src/main/java/com/safetypin/authentication/dto/SocialLoginRequest.java b/src/main/java/com/safetypin/authentication/dto/SocialLoginRequest.java new file mode 100644 index 0000000..1b34b2e --- /dev/null +++ b/src/main/java/com/safetypin/authentication/dto/SocialLoginRequest.java @@ -0,0 +1,35 @@ +package com.safetypin.authentication.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDate; + +@Setter +@Getter +public class SocialLoginRequest { + + @NotBlank + private String provider; // "GOOGLE" or "APPLE" + + @NotBlank + private String socialToken; // Token from the social provider + + // Simulated fields as if retrieved from the provider + @NotBlank + private String email; + + @NotBlank + private String name; + + @NotNull + private LocalDate birthdate; + + @NotBlank + private String socialId; // ID provided by the social provider + + // Getters and setters + +} -- GitLab From 03176bde2547882da4e9f42b6710e357de2fdbe9 Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Wed, 26 Feb 2025 13:52:11 +0700 Subject: [PATCH 17/70] [RED] Add unit tests for User model to validate constructors and field defaults --- .../authentication/model/UserTest.java | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 src/test/java/com/safetypin/authentication/model/UserTest.java diff --git a/src/test/java/com/safetypin/authentication/model/UserTest.java b/src/test/java/com/safetypin/authentication/model/UserTest.java new file mode 100644 index 0000000..2001bd2 --- /dev/null +++ b/src/test/java/com/safetypin/authentication/model/UserTest.java @@ -0,0 +1,82 @@ +package com.safetypin.authentication.model; + +import org.junit.jupiter.api.Test; +import java.time.LocalDate; +import static org.junit.jupiter.api.Assertions.*; + +public class UserTest { + + @Test + public void testDefaultConstructorDefaults() { + User user = new User(); + // Verify that default constructor sets all fields to their default values + assertNull(user.getId(), "Default id should be null"); + assertNull(user.getEmail(), "Default email should be null"); + assertNull(user.getPassword(), "Default password should be null"); + assertNull(user.getName(), "Default name should be null"); + assertFalse(user.isVerified(), "Default isVerified should be false"); + 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 + public void testSettersAndGetters() { + User user = new User(); + Long id = 123L; + String email = "test@example.com"; + String password = "secret"; + String name = "Test User"; + boolean verified = true; + String role = "ADMIN"; + LocalDate birthdate = LocalDate.of(2000, 1, 1); + String provider = "GOOGLE"; + String socialId = "social123"; + + user.setId(id); + user.setEmail(email); + user.setPassword(password); + user.setName(name); + user.setVerified(verified); + user.setRole(role); + user.setBirthdate(birthdate); + user.setProvider(provider); + user.setSocialId(socialId); + + assertEquals(id, user.getId()); + assertEquals(email, user.getEmail()); + assertEquals(password, user.getPassword()); + assertEquals(name, user.getName()); + assertTrue(user.isVerified()); + assertEquals(role, user.getRole()); + assertEquals(birthdate, user.getBirthdate()); + assertEquals(provider, user.getProvider()); + assertEquals(socialId, user.getSocialId()); + } + + @Test + public void testParameterizedConstructor() { + String email = "test2@example.com"; + String password = "password123"; + String name = "Another User"; + boolean verified = false; + String role = "USER"; + LocalDate birthdate = LocalDate.of(1995, 5, 15); + String provider = "EMAIL"; + String socialId = null; + + User user = new User(email, password, name, verified, role, birthdate, provider, socialId); + + // id remains null until set (by the persistence layer) + assertNull(user.getId(), "Id should be null when not set"); + assertEquals(email, user.getEmail()); + assertEquals(password, user.getPassword()); + assertEquals(name, user.getName()); + assertEquals(verified, user.isVerified()); + assertEquals(role, user.getRole()); + assertEquals(birthdate, user.getBirthdate()); + assertEquals(provider, user.getProvider()); + assertNull(user.getSocialId(), "SocialId should be null"); + } +} -- GitLab From 88ffc89ec7c2778db757672c86976aced09baa9c Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Wed, 26 Feb 2025 13:52:33 +0700 Subject: [PATCH 18/70] [GREEN] Add User entity with fields for authentication and social login --- .../safetypin/authentication/model/User.java | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 src/main/java/com/safetypin/authentication/model/User.java diff --git a/src/main/java/com/safetypin/authentication/model/User.java b/src/main/java/com/safetypin/authentication/model/User.java new file mode 100644 index 0000000..c08cb81 --- /dev/null +++ b/src/main/java/com/safetypin/authentication/model/User.java @@ -0,0 +1,76 @@ +package com.safetypin.authentication.model; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDate; + +@Entity +@Table(name = "users") +public class User { + + @Setter + @Getter + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Setter + @Getter + @Column(nullable = false, unique = true) + private String email; + + // May be null for social login users + @Setter + @Getter + @Column(nullable = false) + private String password; + + @Setter + @Getter + @Column(nullable = false) + private String name; + + @Column(nullable = false) + private boolean isVerified = false; + + @Setter + @Getter + private String role; + + // New fields + @Setter + @Getter + private LocalDate birthdate; + @Setter + @Getter + private String provider; // "EMAIL", "GOOGLE", "APPLE" + @Setter + @Getter + private String socialId; // For social login users + + public User() {} + + public User(String email, String password, String name, boolean isVerified, String role, + LocalDate birthdate, String provider, String socialId) { + this.email = email; + this.password = password; + this.name = name; + this.isVerified = isVerified; + this.role = role; + this.birthdate = birthdate; + this.provider = provider; + this.socialId = socialId; + } + + // Getters and setters + + public boolean isVerified() { + return isVerified; + } + public void setVerified(boolean verified) { + isVerified = verified; + } + +} -- GitLab From 374c23bb2afd942744dd6f26b0dad6118ddf2431 Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Wed, 26 Feb 2025 13:52:47 +0700 Subject: [PATCH 19/70] [GREEN] Add custom exceptions for user authentication errors --- .../exception/InvalidCredentialsException.java | 7 +++++++ .../exception/UserAlreadyExistsException.java | 7 +++++++ 2 files changed, 14 insertions(+) create mode 100644 src/main/java/com/safetypin/authentication/exception/InvalidCredentialsException.java create mode 100644 src/main/java/com/safetypin/authentication/exception/UserAlreadyExistsException.java diff --git a/src/main/java/com/safetypin/authentication/exception/InvalidCredentialsException.java b/src/main/java/com/safetypin/authentication/exception/InvalidCredentialsException.java new file mode 100644 index 0000000..e43d14a --- /dev/null +++ b/src/main/java/com/safetypin/authentication/exception/InvalidCredentialsException.java @@ -0,0 +1,7 @@ +package com.safetypin.authentication.exception; + +public class InvalidCredentialsException extends RuntimeException { + public InvalidCredentialsException(String message) { + super(message); + } +} diff --git a/src/main/java/com/safetypin/authentication/exception/UserAlreadyExistsException.java b/src/main/java/com/safetypin/authentication/exception/UserAlreadyExistsException.java new file mode 100644 index 0000000..c31a5a7 --- /dev/null +++ b/src/main/java/com/safetypin/authentication/exception/UserAlreadyExistsException.java @@ -0,0 +1,7 @@ +package com.safetypin.authentication.exception; + +public class UserAlreadyExistsException extends RuntimeException { + public UserAlreadyExistsException(String message) { + super(message); + } +} -- GitLab From 0c05edf95cba5d439875c3e16693b424b816b372 Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Wed, 26 Feb 2025 13:52:56 +0700 Subject: [PATCH 20/70] Add security configuration and password encoder for authentication --- .../security/PasswordEncoderConfig.java | 14 ++++++++ .../security/SecurityConfig.java | 34 +++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 src/main/java/com/safetypin/authentication/security/PasswordEncoderConfig.java create mode 100644 src/main/java/com/safetypin/authentication/security/SecurityConfig.java diff --git a/src/main/java/com/safetypin/authentication/security/PasswordEncoderConfig.java b/src/main/java/com/safetypin/authentication/security/PasswordEncoderConfig.java new file mode 100644 index 0000000..a4e107f --- /dev/null +++ b/src/main/java/com/safetypin/authentication/security/PasswordEncoderConfig.java @@ -0,0 +1,14 @@ +package com.safetypin.authentication.security; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +@Configuration +public class PasswordEncoderConfig { + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/src/main/java/com/safetypin/authentication/security/SecurityConfig.java b/src/main/java/com/safetypin/authentication/security/SecurityConfig.java new file mode 100644 index 0000000..c79b353 --- /dev/null +++ b/src/main/java/com/safetypin/authentication/security/SecurityConfig.java @@ -0,0 +1,34 @@ +package com.safetypin.authentication.security; + + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +public class SecurityConfig { + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .csrf(AbstractHttpConfigurer::disable) // Disable CSRF protection (not recommended for production) + .authorizeHttpRequests(auth -> auth + .requestMatchers("/**").permitAll() // Allow all requests + ) + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // No session + .formLogin(AbstractHttpConfigurer::disable) // Disable login page + .httpBasic(AbstractHttpConfigurer::disable); // Disable basic authentication + + return http.build(); + } + + @Bean + public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { + return authenticationConfiguration.getAuthenticationManager(); + } +} -- GitLab From 14b0c0834a2f6c75840f4addcc3ebd993ada5510 Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Wed, 26 Feb 2025 13:53:02 +0700 Subject: [PATCH 21/70] Add DevDataSeeder to populate dummy users in the development environment --- .../authentication/seeder/DevDataSeeder.java | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java diff --git a/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java b/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java new file mode 100644 index 0000000..9edfd57 --- /dev/null +++ b/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java @@ -0,0 +1,48 @@ +package com.safetypin.authentication.seeder; + +import com.safetypin.authentication.model.User; +import com.safetypin.authentication.repository.UserRepository; +import org.springframework.boot.CommandLineRunner; +import org.springframework.context.annotation.Profile; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Component; + +import java.time.LocalDate; +import java.util.List; + +@Component +@Profile({"dev"}) // Runs only in 'dev' profile +public class DevDataSeeder implements CommandLineRunner { + + private final UserRepository userRepository; + + private final PasswordEncoder passwordEncoder; + + public DevDataSeeder(UserRepository userRepository, PasswordEncoder passwordEncoder) { + this.userRepository = userRepository; + this.passwordEncoder = passwordEncoder; + } + + @Override + public void run(String... args) { + if (userRepository.count() == 0) { + userRepository.saveAll(List.of( + new User("alice@example.com", passwordEncoder.encode("password123"), "Alice Johnson", true, "developer", + LocalDate.of(1998, 5, 21), "EMAIL", "social_1001"), + new User("bob@example.com", passwordEncoder.encode("password456"), "Bob Smith", false, "designer", + LocalDate.of(2000, 8, 15), "GOOGLE", "social_1002"), + new User("charlie@example.com", passwordEncoder.encode("password789"), "Charlie Davis", true, "manager", + LocalDate.of(1995, 12, 3), "APPLE", "social_1003"), + new User("diana@example.com", passwordEncoder.encode("password321"), "Diana Roberts", true, "QA engineer", + LocalDate.of(2002, 6, 10), "EMAIL", "social_1004"), + new User("ethan@example.com", passwordEncoder.encode("password654"), "Ethan Brown", false, "data analyst", + LocalDate.of(1999, 11, 27), "EMAIL", "social_1005") + )); + System.out.println("Dummy users inserted in DEV environment"); + } else { + System.out.println("User repo is not empty"); + } + + + } +} \ No newline at end of file -- GitLab From c6aa639857ca0775dd3077f70250626ae00a8b84 Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Wed, 26 Feb 2025 14:50:47 +0700 Subject: [PATCH 22/70] Add JNA dependency for testing and remove DtoTest class; configure H2 in-memory database for tests --- pom.xml | 7 ++ .../safetypin/authentication/dto/DtoTest.java | 93 ------------------- src/test/resources/application-dev.properties | 14 +++ 3 files changed, 21 insertions(+), 93 deletions(-) delete mode 100644 src/test/java/com/safetypin/authentication/dto/DtoTest.java create mode 100644 src/test/resources/application-dev.properties diff --git a/pom.xml b/pom.xml index 7399793..bfd64b5 100644 --- a/pom.xml +++ b/pom.xml @@ -154,6 +154,13 @@ <scope>test</scope> </dependency> + <dependency> + <groupId>net.java.dev.jna</groupId> + <artifactId>jna-platform</artifactId> + <version>5.13.0</version> + <scope>test</scope> + </dependency> + </dependencies> diff --git a/src/test/java/com/safetypin/authentication/dto/DtoTest.java b/src/test/java/com/safetypin/authentication/dto/DtoTest.java deleted file mode 100644 index 645db7e..0000000 --- a/src/test/java/com/safetypin/authentication/dto/DtoTest.java +++ /dev/null @@ -1,93 +0,0 @@ -package com.safetypin.authentication.dto; - -import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.assertThat; -import jakarta.validation.Validation; -import jakarta.validation.Validator; -import jakarta.validation.ValidatorFactory; -import jakarta.validation.ConstraintViolation; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.Set; - -public class DtoTest { - - private final Validator validator; - - public DtoTest() { - ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); - this.validator = factory.getValidator(); - } - - @Test - void testErrorResponseConstructor() { - ErrorResponse errorResponse = new ErrorResponse(404, "Resource not found"); - - assertThat(errorResponse.getStatus()).isEqualTo(404); - assertThat(errorResponse.getMessage()).isEqualTo("Resource not found"); - assertThat(errorResponse.getTimestamp()).isNotNull(); - assertThat(errorResponse.getTimestamp()).isBeforeOrEqualTo(LocalDateTime.now()); - } - - @Test - void testPasswordResetRequestValid() { - PasswordResetRequest request = new PasswordResetRequest(); - request.setEmail("user@example.com"); - - Set<ConstraintViolation<PasswordResetRequest>> violations = validator.validate(request); - assertThat(violations).isEmpty(); - } - - @Test - void testPasswordResetRequestInvalidEmail() { - PasswordResetRequest request = new PasswordResetRequest(); - request.setEmail("invalid-email"); - - Set<ConstraintViolation<PasswordResetRequest>> violations = validator.validate(request); - assertThat(violations).isNotEmpty(); - } - - @Test - void testRegistrationRequestValid() { - RegistrationRequest request = new RegistrationRequest(); - request.setEmail("user@example.com"); - request.setPassword("securePassword"); - request.setName("John Doe"); - request.setBirthdate(LocalDate.of(1995, 5, 10)); - - Set<ConstraintViolation<RegistrationRequest>> violations = validator.validate(request); - assertThat(violations).isEmpty(); - } - - @Test - void testRegistrationRequestMissingFields() { - RegistrationRequest request = new RegistrationRequest(); // Missing required fields - - Set<ConstraintViolation<RegistrationRequest>> violations = validator.validate(request); - assertThat(violations).isNotEmpty(); - assertThat(violations).hasSize(4); // Email, password, name, and birthdate should all be invalid - } - - @Test - void testSocialLoginRequestValid() { - SocialLoginRequest request = new SocialLoginRequest(); - request.setProvider("GOOGLE"); - request.setSocialToken("validToken"); - request.setEmail("socialuser@example.com"); - request.setName("Social User"); - request.setBirthdate(LocalDate.of(2000, 1, 1)); - request.setSocialId("123456789"); - - Set<ConstraintViolation<SocialLoginRequest>> violations = validator.validate(request); - assertThat(violations).isEmpty(); - } - - @Test - void testSocialLoginRequestMissingFields() { - SocialLoginRequest request = new SocialLoginRequest(); // Missing required fields - - Set<ConstraintViolation<SocialLoginRequest>> violations = validator.validate(request); - assertThat(violations).isNotEmpty(); - assertThat(violations).hasSize(6); // All fields should be invalid - } -} diff --git a/src/test/resources/application-dev.properties b/src/test/resources/application-dev.properties new file mode 100644 index 0000000..e42e68f --- /dev/null +++ b/src/test/resources/application-dev.properties @@ -0,0 +1,14 @@ +# src/test/resources/application-dev.properties + +# Configure H2 in-memory database +spring.datasource.url=jdbc:h2:mem:devdb;DB_CLOSE_DELAY=-1 +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password= + +# JPA/Hibernate configurations +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +spring.jpa.hibernate.ddl-auto=create-drop + +# (Optional) Enable SQL logging +spring.jpa.show-sql=true -- GitLab From 2f2123477f32c94c94fd50842109fc53f2fcc026 Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Wed, 26 Feb 2025 14:51:21 +0700 Subject: [PATCH 23/70] [REFACTOR] Add unit tests for error response and request validation in authentication DTOs --- .../authentication/dto/ErrorResponseTest.java | 18 ++++++++ .../dto/PasswordResetRequestTest.java | 37 ++++++++++++++++ .../dto/RegistrationRequestTest.java | 42 ++++++++++++++++++ .../dto/SocialLoginRequestTest.java | 44 +++++++++++++++++++ 4 files changed, 141 insertions(+) create mode 100644 src/test/java/com/safetypin/authentication/dto/ErrorResponseTest.java create mode 100644 src/test/java/com/safetypin/authentication/dto/PasswordResetRequestTest.java create mode 100644 src/test/java/com/safetypin/authentication/dto/RegistrationRequestTest.java create mode 100644 src/test/java/com/safetypin/authentication/dto/SocialLoginRequestTest.java diff --git a/src/test/java/com/safetypin/authentication/dto/ErrorResponseTest.java b/src/test/java/com/safetypin/authentication/dto/ErrorResponseTest.java new file mode 100644 index 0000000..1398d5a --- /dev/null +++ b/src/test/java/com/safetypin/authentication/dto/ErrorResponseTest.java @@ -0,0 +1,18 @@ +package com.safetypin.authentication.dto; + +import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; +import java.time.LocalDateTime; + +class ErrorResponseTest { + + @Test + void testErrorResponseConstructor() { + ErrorResponse errorResponse = new ErrorResponse(404, "Resource not found"); + + assertThat(errorResponse.getStatus()).isEqualTo(404); + assertThat(errorResponse.getMessage()).isEqualTo("Resource not found"); + assertThat(errorResponse.getTimestamp()).isNotNull(); + assertThat(errorResponse.getTimestamp()).isBeforeOrEqualTo(LocalDateTime.now()); + } +} diff --git a/src/test/java/com/safetypin/authentication/dto/PasswordResetRequestTest.java b/src/test/java/com/safetypin/authentication/dto/PasswordResetRequestTest.java new file mode 100644 index 0000000..013ec5d --- /dev/null +++ b/src/test/java/com/safetypin/authentication/dto/PasswordResetRequestTest.java @@ -0,0 +1,37 @@ +package com.safetypin.authentication.dto; + +import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import jakarta.validation.ValidatorFactory; +import jakarta.validation.ConstraintViolation; +import java.util.Set; + +class PasswordResetRequestTest { + + private final Validator validator; + + public PasswordResetRequestTest() { + ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); + this.validator = factory.getValidator(); + } + + @Test + void testPasswordResetRequestValid() { + PasswordResetRequest request = new PasswordResetRequest(); + request.setEmail("user@example.com"); + + Set<ConstraintViolation<PasswordResetRequest>> violations = validator.validate(request); + assertThat(violations).isEmpty(); + } + + @Test + void testPasswordResetRequestInvalidEmail() { + PasswordResetRequest request = new PasswordResetRequest(); + request.setEmail("invalid-email"); + + Set<ConstraintViolation<PasswordResetRequest>> violations = validator.validate(request); + assertThat(violations).isNotEmpty(); + } +} diff --git a/src/test/java/com/safetypin/authentication/dto/RegistrationRequestTest.java b/src/test/java/com/safetypin/authentication/dto/RegistrationRequestTest.java new file mode 100644 index 0000000..b231e67 --- /dev/null +++ b/src/test/java/com/safetypin/authentication/dto/RegistrationRequestTest.java @@ -0,0 +1,42 @@ +package com.safetypin.authentication.dto; + +import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import jakarta.validation.ValidatorFactory; +import jakarta.validation.ConstraintViolation; +import java.time.LocalDate; +import java.util.Set; + +class RegistrationRequestTest { + + private final Validator validator; + + public RegistrationRequestTest() { + ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); + this.validator = factory.getValidator(); + } + + @Test + void testRegistrationRequestValid() { + RegistrationRequest request = new RegistrationRequest(); + request.setEmail("user@example.com"); + request.setPassword("securePassword"); + request.setName("John Doe"); + request.setBirthdate(LocalDate.of(1995, 5, 10)); + + Set<ConstraintViolation<RegistrationRequest>> violations = validator.validate(request); + assertThat(violations).isEmpty(); + } + + @Test + void testRegistrationRequestMissingFields() { + RegistrationRequest request = new RegistrationRequest(); // Missing required fields + + Set<ConstraintViolation<RegistrationRequest>> violations = validator.validate(request); + assertThat(violations) + .isNotEmpty() + .hasSize(4); // Email, password, name, and birthdate should all be invalid + } +} diff --git a/src/test/java/com/safetypin/authentication/dto/SocialLoginRequestTest.java b/src/test/java/com/safetypin/authentication/dto/SocialLoginRequestTest.java new file mode 100644 index 0000000..fe4a2a6 --- /dev/null +++ b/src/test/java/com/safetypin/authentication/dto/SocialLoginRequestTest.java @@ -0,0 +1,44 @@ +package com.safetypin.authentication.dto; + +import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import jakarta.validation.ValidatorFactory; +import jakarta.validation.ConstraintViolation; +import java.time.LocalDate; +import java.util.Set; + +class SocialLoginRequestTest { + + private final Validator validator; + + public SocialLoginRequestTest() { + ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); + this.validator = factory.getValidator(); + } + + @Test + void testSocialLoginRequestValid() { + SocialLoginRequest request = new SocialLoginRequest(); + request.setProvider("GOOGLE"); + request.setSocialToken("validToken"); + request.setEmail("socialuser@example.com"); + request.setName("Social User"); + request.setBirthdate(LocalDate.of(2000, 1, 1)); + request.setSocialId("123456789"); + + Set<ConstraintViolation<SocialLoginRequest>> violations = validator.validate(request); + assertThat(violations).isEmpty(); + } + + @Test + void testSocialLoginRequestMissingFields() { + SocialLoginRequest request = new SocialLoginRequest(); // Missing required fields + + Set<ConstraintViolation<SocialLoginRequest>> violations = validator.validate(request); + assertThat(violations) + .isNotEmpty() + .hasSize(6); // All fields should be invalid + } +} -- GitLab From d9732c644e1d4398d50243b4af5caf49ee9a6349 Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Wed, 26 Feb 2025 14:51:44 +0700 Subject: [PATCH 24/70] [RED] Add unit tests for DevDataSeeder to verify user insertion logic --- .../seeder/DevDataSeederTest.java | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 src/test/java/com/safetypin/authentication/seeder/DevDataSeederTest.java diff --git a/src/test/java/com/safetypin/authentication/seeder/DevDataSeederTest.java b/src/test/java/com/safetypin/authentication/seeder/DevDataSeederTest.java new file mode 100644 index 0000000..8b58e96 --- /dev/null +++ b/src/test/java/com/safetypin/authentication/seeder/DevDataSeederTest.java @@ -0,0 +1,60 @@ +package com.safetypin.authentication.seeder; + +import com.safetypin.authentication.model.User; +import com.safetypin.authentication.repository.UserRepository; +import jakarta.transaction.Transactional; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.test.context.ActiveProfiles; + +import java.time.LocalDate; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SpringBootTest +@ActiveProfiles("dev") // Use the 'dev' profile during tests +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@Transactional +class DevDataSeederTest { + + @Autowired + private UserRepository userRepository; + + @Autowired + private PasswordEncoder passwordEncoder; + + // Test that the seeder inserts 5 users when no users exist + @Test + void testSeederInsertsUsersWhenEmpty() { + userRepository.deleteAll(); // Ensure the database is empty before seeding + + new DevDataSeeder(userRepository, passwordEncoder).run(); // Run the seeder + + List<User> users = userRepository.findAll(); + assertEquals(5, users.size(), "Seeder should insert 5 users when repository is empty"); + } + + // Test that the seeder does not add any users if at least one user already exists + @Test + void testSeederDoesNotInsertIfUsersExist() { + // Save an existing user into the repository + userRepository.save(new User("existing@example.com", + passwordEncoder.encode("test"), + "Existing User", + true, + "admin", + LocalDate.of(1990, 1, 1), + "EMAIL", + "social_9999")); + + long countBefore = userRepository.count(); + new DevDataSeeder(userRepository, passwordEncoder).run(); + long countAfter = userRepository.count(); + + assertEquals(countBefore, countAfter, "Seeder should not insert new users if users already exist"); + } +} -- GitLab From 22dd758f4bb3bd491ded36ed6b5653ae1e87649a Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Wed, 26 Feb 2025 14:51:53 +0700 Subject: [PATCH 25/70] [REFACTOR] Update DevDataSeeder to use Runnable interface and simplify user seeding logic --- .../authentication/seeder/DevDataSeeder.java | 73 ++++++++++++------- 1 file changed, 47 insertions(+), 26 deletions(-) diff --git a/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java b/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java index 9edfd57..e33c4f6 100644 --- a/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java +++ b/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java @@ -2,20 +2,13 @@ package com.safetypin.authentication.seeder; import com.safetypin.authentication.model.User; import com.safetypin.authentication.repository.UserRepository; -import org.springframework.boot.CommandLineRunner; -import org.springframework.context.annotation.Profile; import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Component; import java.time.LocalDate; -import java.util.List; -@Component -@Profile({"dev"}) // Runs only in 'dev' profile -public class DevDataSeeder implements CommandLineRunner { +public class DevDataSeeder implements Runnable { private final UserRepository userRepository; - private final PasswordEncoder passwordEncoder; public DevDataSeeder(UserRepository userRepository, PasswordEncoder passwordEncoder) { @@ -24,25 +17,53 @@ public class DevDataSeeder implements CommandLineRunner { } @Override - public void run(String... args) { + public void run() { + // Only seed if there are no users in the repository if (userRepository.count() == 0) { - userRepository.saveAll(List.of( - new User("alice@example.com", passwordEncoder.encode("password123"), "Alice Johnson", true, "developer", - LocalDate.of(1998, 5, 21), "EMAIL", "social_1001"), - new User("bob@example.com", passwordEncoder.encode("password456"), "Bob Smith", false, "designer", - LocalDate.of(2000, 8, 15), "GOOGLE", "social_1002"), - new User("charlie@example.com", passwordEncoder.encode("password789"), "Charlie Davis", true, "manager", - LocalDate.of(1995, 12, 3), "APPLE", "social_1003"), - new User("diana@example.com", passwordEncoder.encode("password321"), "Diana Roberts", true, "QA engineer", - LocalDate.of(2002, 6, 10), "EMAIL", "social_1004"), - new User("ethan@example.com", passwordEncoder.encode("password654"), "Ethan Brown", false, "data analyst", - LocalDate.of(1999, 11, 27), "EMAIL", "social_1005") - )); - System.out.println("Dummy users inserted in DEV environment"); - } else { - System.out.println("User repo is not empty"); - } + userRepository.save(new User("user1@example.com", + passwordEncoder.encode("password1"), + "User One", + true, + "user", + LocalDate.of(1990, 1, 1), + "EMAIL", + "social1")); + + userRepository.save(new User("user2@example.com", + passwordEncoder.encode("password2"), + "User Two", + true, + "user", + LocalDate.of(1991, 2, 2), + "EMAIL", + "social2")); + userRepository.save(new User("user3@example.com", + passwordEncoder.encode("password3"), + "User Three", + true, + "user", + LocalDate.of(1992, 3, 3), + "EMAIL", + "social3")); + userRepository.save(new User("user4@example.com", + passwordEncoder.encode("password4"), + "User Four", + true, + "user", + LocalDate.of(1993, 4, 4), + "EMAIL", + "social4")); + + userRepository.save(new User("user5@example.com", + passwordEncoder.encode("password5"), + "User Five", + true, + "user", + LocalDate.of(1994, 5, 5), + "EMAIL", + "social5")); + } } -} \ No newline at end of file +} -- GitLab From 7075b72a832a050146f366e5f809d9a63d30cefd Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Wed, 26 Feb 2025 14:52:02 +0700 Subject: [PATCH 26/70] [REFACTOR] Update AuthenticationControllerTest to remove redundant access modifiers --- .../AuthenticationControllerTest.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java b/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java index 9d335f4..8364783 100644 --- a/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java +++ b/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java @@ -28,7 +28,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @WebMvcTest(AuthenticationController.class) @Import({AuthenticationControllerTest.TestConfig.class, AuthenticationControllerTest.TestSecurityConfig.class}) -public class AuthenticationControllerTest { +class AuthenticationControllerTest { @Autowired private MockMvc mockMvc; @@ -52,13 +52,13 @@ public class AuthenticationControllerTest { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.csrf(csrf -> csrf.disable()) - .authorizeHttpRequests(authz -> authz.anyRequest().permitAll()); + .authorizeHttpRequests(authz -> authz.anyRequest().permitAll()) return http.build(); } } @Test - public void testRegisterEmail() throws Exception { + void testRegisterEmail() throws Exception { RegistrationRequest request = new RegistrationRequest(); request.setEmail("email@example.com"); request.setPassword("password"); @@ -79,7 +79,7 @@ public class AuthenticationControllerTest { } @Test - public void testRegisterSocial() throws Exception { + void testRegisterSocial() throws Exception { SocialLoginRequest request = new SocialLoginRequest(); request.setProvider("GOOGLE"); request.setSocialToken("token"); @@ -102,7 +102,7 @@ public class AuthenticationControllerTest { } @Test - public void testLoginEmail() throws Exception { + void testLoginEmail() throws Exception { User user = new User("email@example.com", "encodedPassword", "Test User", true, "USER", LocalDate.now().minusYears(20), "EMAIL", null); user.setId(1L); @@ -117,7 +117,7 @@ public class AuthenticationControllerTest { } @Test - public void testLoginSocial() throws Exception { + void testLoginSocial() throws Exception { User user = new User("social@example.com", null, "Social User", true, "USER", LocalDate.now().minusYears(25), "GOOGLE", "social123"); user.setId(2L); @@ -131,7 +131,7 @@ public class AuthenticationControllerTest { } @Test - public void testVerifyOTP_Success() throws Exception { + void testVerifyOTP_Success() throws Exception { Mockito.when(authenticationService.verifyOTP(eq("email@example.com"), eq("123456"))).thenReturn(true); mockMvc.perform(post("/api/auth/verify-otp") @@ -142,7 +142,7 @@ public class AuthenticationControllerTest { } @Test - public void testVerifyOTP_Failure() throws Exception { + void testVerifyOTP_Failure() throws Exception { Mockito.when(authenticationService.verifyOTP(eq("email@example.com"), eq("000000"))).thenReturn(false); mockMvc.perform(post("/api/auth/verify-otp") @@ -153,7 +153,7 @@ public class AuthenticationControllerTest { } @Test - public void testForgotPassword() throws Exception { + void testForgotPassword() throws Exception { PasswordResetRequest request = new PasswordResetRequest(); request.setEmail("email@example.com"); @@ -167,7 +167,7 @@ public class AuthenticationControllerTest { } @Test - public void testPostContent() throws Exception { + void testPostContent() throws Exception { Mockito.when(authenticationService.postContent(eq("email@example.com"), eq("Test Content"))) .thenReturn("Content posted successfully"); @@ -179,7 +179,7 @@ public class AuthenticationControllerTest { } @Test - public void testDashboard() throws Exception { + void testDashboard() throws Exception { mockMvc.perform(get("/api/auth/dashboard")) .andExpect(status().isOk()) .andExpect(content().string("{}")); -- GitLab From a486627f9a80d3a1f7836c1dfd6b10c8b738e0b3 Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Wed, 26 Feb 2025 14:52:07 +0700 Subject: [PATCH 27/70] [REFACTOR] Replace Random with SecureRandom in OTPService for improved security --- .../com/safetypin/authentication/service/OTPService.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/safetypin/authentication/service/OTPService.java b/src/main/java/com/safetypin/authentication/service/OTPService.java index ac9ee7d..b338aca 100644 --- a/src/main/java/com/safetypin/authentication/service/OTPService.java +++ b/src/main/java/com/safetypin/authentication/service/OTPService.java @@ -3,8 +3,9 @@ package com.safetypin.authentication.service; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; + +import java.security.SecureRandom; import java.time.LocalDateTime; -import java.util.Random; import java.util.concurrent.ConcurrentHashMap; @Service @@ -13,7 +14,7 @@ public class OTPService { private static final long OTP_EXPIRATION_SECONDS = 120; // 2 minutes expiration private static final Logger log = LoggerFactory.getLogger(OTPService.class); private final ConcurrentHashMap<String, OTPDetails> otpStorage = new ConcurrentHashMap<>(); - private final Random random = new Random(); + private final SecureRandom random = new SecureRandom(); public String generateOTP(String email) { String otp = String.format("%06d", random.nextInt(1000000)); -- GitLab From 9209188d76c389dbd334514c00f207afb0224e5f Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Wed, 26 Feb 2025 15:04:03 +0700 Subject: [PATCH 28/70] [REFACTOR] Fix syntax error in AuthenticationControllerTest for improved readability --- .../controller/AuthenticationControllerTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java b/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java index 8364783..5d3df22 100644 --- a/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java +++ b/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java @@ -51,9 +51,9 @@ class AuthenticationControllerTest { static class TestSecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http.csrf(csrf -> csrf.disable()) - .authorizeHttpRequests(authz -> authz.anyRequest().permitAll()) - return http.build(); + http.csrf(csrf -> csrf.disable()); + .authorizeHttpRequests(authorize -> authorize.anyRequest().permitAll()); + return http.build(); } } -- GitLab From 01f6fb0fdc96a614ef509f2b42911be2b14b5110 Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Wed, 26 Feb 2025 15:15:46 +0700 Subject: [PATCH 29/70] [REFACTOR] Fix indentation issue in AuthenticationControllerTest for improved readability --- .../controller/AuthenticationControllerTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java b/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java index 5d3df22..2908d9a 100644 --- a/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java +++ b/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java @@ -51,8 +51,7 @@ class AuthenticationControllerTest { static class TestSecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http.csrf(csrf -> csrf.disable()); - .authorizeHttpRequests(authorize -> authorize.anyRequest().permitAll()); + http.authorizeHttpRequests(authorize -> authorize.anyRequest().permitAll()); return http.build(); } } -- GitLab From ed4bc1ed75c76058efd24272b1f8075bb4a7fe55 Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Wed, 26 Feb 2025 15:15:53 +0700 Subject: [PATCH 30/70] [REFACTOR] Remove redundant access modifiers in AuthenticationServiceTest for improved clarity --- .../service/AuthenticationServiceTest.java | 45 +++++++++---------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java b/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java index 7064385..bdf05fe 100644 --- a/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java +++ b/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java @@ -17,11 +17,10 @@ import java.time.LocalDate; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) -public class AuthenticationServiceTest { +class AuthenticationServiceTest { @Mock private UserRepository userRepository; @@ -38,7 +37,7 @@ public class AuthenticationServiceTest { // registerUser tests @Test - public void testRegisterUser_UnderAge() { + void testRegisterUser_UnderAge() { RegistrationRequest request = new RegistrationRequest(); request.setEmail("test@example.com"); request.setPassword("password"); @@ -53,7 +52,7 @@ public class AuthenticationServiceTest { } @Test - public void testRegisterUser_DuplicateEmail() { + void testRegisterUser_DuplicateEmail() { RegistrationRequest request = new RegistrationRequest(); request.setEmail("test@example.com"); request.setPassword("password"); @@ -69,7 +68,7 @@ public class AuthenticationServiceTest { } @Test - public void testRegisterUser_Success() { + void testRegisterUser_Success() { RegistrationRequest request = new RegistrationRequest(); request.setEmail("test@example.com"); request.setPassword("password"); @@ -93,7 +92,7 @@ public class AuthenticationServiceTest { // socialLogin tests @Test - public void testSocialLogin_UnderAge() { + void testSocialLogin_UnderAge() { SocialLoginRequest request = new SocialLoginRequest(); request.setEmail("social@example.com"); request.setName("Social User"); @@ -109,7 +108,7 @@ public class AuthenticationServiceTest { } @Test - public void testSocialLogin_DuplicateEmailWithEmailProvider() { + void testSocialLogin_DuplicateEmailWithEmailProvider() { SocialLoginRequest request = new SocialLoginRequest(); request.setEmail("social@example.com"); request.setName("Social User"); @@ -129,7 +128,7 @@ public class AuthenticationServiceTest { } @Test - public void testSocialLogin_ExistingSocialUser() { + void testSocialLogin_ExistingSocialUser() { SocialLoginRequest request = new SocialLoginRequest(); request.setEmail("social@example.com"); request.setName("Social User"); @@ -148,7 +147,7 @@ public class AuthenticationServiceTest { } @Test - public void testSocialLogin_NewUser() { + void testSocialLogin_NewUser() { SocialLoginRequest request = new SocialLoginRequest(); request.setEmail("social@example.com"); request.setName("Social User"); @@ -171,7 +170,7 @@ public class AuthenticationServiceTest { // loginUser tests @Test - public void testLoginUser_EmailNotFound() { + void testLoginUser_EmailNotFound() { when(userRepository.findByEmail("notfound@example.com")).thenReturn(null); Exception exception = assertThrows(InvalidCredentialsException.class, () -> authenticationService.loginUser("notfound@example.com", "password") @@ -180,7 +179,7 @@ public class AuthenticationServiceTest { } @Test - public void testLoginUser_InvalidPassword_NullPassword() { + void testLoginUser_InvalidPassword_NullPassword() { User user = new User("test@example.com", null, "Test User", true, "USER", LocalDate.now().minusYears(20), "EMAIL", null); when(userRepository.findByEmail("test@example.com")).thenReturn(user); @@ -192,7 +191,7 @@ public class AuthenticationServiceTest { } @Test - public void testLoginUser_InvalidPassword_WrongMatch() { + void testLoginUser_InvalidPassword_WrongMatch() { User user = new User("test@example.com", "encodedPassword", "Test User", true, "USER", LocalDate.now().minusYears(20), "EMAIL", null); when(userRepository.findByEmail("test@example.com")).thenReturn(user); @@ -205,7 +204,7 @@ public class AuthenticationServiceTest { } @Test - public void testLoginUser_Success() { + void testLoginUser_Success() { User user = new User("test@example.com", "encodedPassword", "Test User", true, "USER", LocalDate.now().minusYears(20), "EMAIL", null); when(userRepository.findByEmail("test@example.com")).thenReturn(user); @@ -219,7 +218,7 @@ public class AuthenticationServiceTest { // loginSocial tests @Test - public void testLoginSocial_UserNotFound() { + void testLoginSocial_UserNotFound() { when(userRepository.findByEmail("notfound@example.com")).thenReturn(null); Exception exception = assertThrows(InvalidCredentialsException.class, () -> authenticationService.loginSocial("notfound@example.com") @@ -228,7 +227,7 @@ public class AuthenticationServiceTest { } @Test - public void testLoginSocial_Success() { + void testLoginSocial_Success() { User user = new User("social@example.com", null, "Social User", true, "USER", LocalDate.now().minusYears(25), "GOOGLE", "social123"); when(userRepository.findByEmail("social@example.com")).thenReturn(user); @@ -241,7 +240,7 @@ public class AuthenticationServiceTest { // verifyOTP tests @Test - public void testVerifyOTP_Success() { + void testVerifyOTP_Success() { // OTPService returns true and user is found when(otpService.verifyOTP("test@example.com", "123456")).thenReturn(true); User user = new User("test@example.com", "encodedPassword", "Test User", false, "USER", @@ -256,7 +255,7 @@ public class AuthenticationServiceTest { } @Test - public void testVerifyOTP_Success_UserNotFound() { + 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); @@ -267,7 +266,7 @@ public class AuthenticationServiceTest { } @Test - public void testVerifyOTP_Failure() { + void testVerifyOTP_Failure() { when(otpService.verifyOTP("test@example.com", "000000")).thenReturn(false); boolean result = authenticationService.verifyOTP("test@example.com", "000000"); assertFalse(result); @@ -277,7 +276,7 @@ public class AuthenticationServiceTest { // forgotPassword tests @Test - public void testForgotPassword_Success() { + void testForgotPassword_Success() { User user = new User("test@example.com", "encodedPassword", "Test User", true, "USER", LocalDate.now().minusYears(20), "EMAIL", null); when(userRepository.findByEmail("test@example.com")).thenReturn(user); @@ -286,7 +285,7 @@ public class AuthenticationServiceTest { } @Test - public void testForgotPassword_Invalid() { + void testForgotPassword_Invalid() { // Case 1: user not found when(userRepository.findByEmail("notfound@example.com")).thenReturn(null); Exception exception1 = assertThrows(IllegalArgumentException.class, () -> @@ -307,14 +306,14 @@ public class AuthenticationServiceTest { // postContent tests @Test - public void testPostContent_UserNotFound() { + 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 - public void testPostContent_UserNotVerified() { + void testPostContent_UserNotVerified() { User user = new User("test@example.com", "encodedPassword", "Test User", false, "USER", LocalDate.now().minusYears(20), "EMAIL", null); when(userRepository.findByEmail("test@example.com")).thenReturn(user); @@ -323,7 +322,7 @@ public class AuthenticationServiceTest { } @Test - public void testPostContent_UserVerified() { + void testPostContent_UserVerified() { User user = new User("test@example.com", "encodedPassword", "Test User", true, "USER", LocalDate.now().minusYears(20), "EMAIL", null); when(userRepository.findByEmail("test@example.com")).thenReturn(user); -- GitLab From bb1325fdfa31ef9c92335967effb19db189a495a Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Wed, 26 Feb 2025 15:15:57 +0700 Subject: [PATCH 31/70] [REFACTOR] Remove unused import in AuthenticationController for cleaner code --- .../authentication/controller/AuthenticationController.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java b/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java index 9b09ad8..a42494e 100644 --- a/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java +++ b/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java @@ -9,7 +9,6 @@ import com.safetypin.authentication.service.AuthenticationService; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import jakarta.validation.Valid; -import com.safetypin.authentication.dto.ErrorResponse; import org.springframework.web.bind.annotation.*; @RestController -- GitLab From ad11accf04108acb3421a6c4bb7c3bb8e2d262b0 Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Wed, 26 Feb 2025 15:16:01 +0700 Subject: [PATCH 32/70] [REFACTOR] Change access modifiers in UserTest for improved encapsulation --- .../com/safetypin/authentication/model/UserTest.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/test/java/com/safetypin/authentication/model/UserTest.java b/src/test/java/com/safetypin/authentication/model/UserTest.java index 2001bd2..f66bde4 100644 --- a/src/test/java/com/safetypin/authentication/model/UserTest.java +++ b/src/test/java/com/safetypin/authentication/model/UserTest.java @@ -1,13 +1,15 @@ package com.safetypin.authentication.model; import org.junit.jupiter.api.Test; + import java.time.LocalDate; + import static org.junit.jupiter.api.Assertions.*; -public class UserTest { +class UserTest { @Test - public void testDefaultConstructorDefaults() { + void testDefaultConstructorDefaults() { User user = new User(); // Verify that default constructor sets all fields to their default values assertNull(user.getId(), "Default id should be null"); @@ -22,7 +24,7 @@ public class UserTest { } @Test - public void testSettersAndGetters() { + void testSettersAndGetters() { User user = new User(); Long id = 123L; String email = "test@example.com"; @@ -56,7 +58,7 @@ public class UserTest { } @Test - public void testParameterizedConstructor() { + void testParameterizedConstructor() { String email = "test2@example.com"; String password = "password123"; String name = "Another User"; -- GitLab From 997a2f648acfe5acf67a94163cdb44f4712b16ed Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Wed, 26 Feb 2025 15:21:27 +0700 Subject: [PATCH 33/70] [REFACTOR] Update SecurityConfig to clarify CSRF protection handling --- .../com/safetypin/authentication/security/SecurityConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/safetypin/authentication/security/SecurityConfig.java b/src/main/java/com/safetypin/authentication/security/SecurityConfig.java index c79b353..dae1a16 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(AbstractHttpConfigurer::disable) // Disable CSRF protection (not recommended for production) + // CSRF protection is enabled by default, so we don't disable it here .authorizeHttpRequests(auth -> auth .requestMatchers("/**").permitAll() // Allow all requests ) -- GitLab From 204e9066e8a030e41b12a3180a333f6b4e3dca89 Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Wed, 26 Feb 2025 15:55:41 +0700 Subject: [PATCH 34/70] [REFACTOR] Update dependencies in pom.xml for improved testing support --- pom.xml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index bfd64b5..a35f43c 100644 --- a/pom.xml +++ b/pom.xml @@ -149,15 +149,17 @@ </dependency> <dependency> - <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-starter-test</artifactId> + <groupId>net.java.dev.jna</groupId> + <artifactId>jna-platform</artifactId> + <version>5.13.0</version> <scope>test</scope> </dependency> + <dependency> - <groupId>net.java.dev.jna</groupId> - <artifactId>jna-platform</artifactId> - <version>5.13.0</version> + <groupId>org.jacoco</groupId> + <artifactId>org.jacoco.agent</artifactId> + <version>0.8.11</version> <scope>test</scope> </dependency> -- GitLab From 260d989e87e8bff9895b2cc142a410a25e802615 Mon Sep 17 00:00:00 2001 From: Fredo <fredotanzil@gmail.com> Date: Wed, 26 Feb 2025 17:04:24 +0700 Subject: [PATCH 35/70] [REFACTOR] remove todo --- .../authentication/controller/AuthenticationController.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java b/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java index a42494e..08f921c 100644 --- a/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java +++ b/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java @@ -49,7 +49,6 @@ public class AuthenticationController { try { return ResponseEntity.ok(authenticationService.loginUser(email, password)); } catch (InvalidCredentialsException e){ - //TODO return ResponseEntity.status(HttpStatus.UNAUTHORIZED) .body(e.getMessage()); } -- GitLab From bc8b933664de482e087d2df967d30ee7e7d66631 Mon Sep 17 00:00:00 2001 From: riorio805 <sefrianojieftara@gmail.com> Date: Wed, 26 Feb 2025 17:41:00 +0700 Subject: [PATCH 36/70] Create AuthResponse DTO to standardize between success and failed response --- .../safetypin/authentication/dto/AuthResponse.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/main/java/com/safetypin/authentication/dto/AuthResponse.java diff --git a/src/main/java/com/safetypin/authentication/dto/AuthResponse.java b/src/main/java/com/safetypin/authentication/dto/AuthResponse.java new file mode 100644 index 0000000..a29962f --- /dev/null +++ b/src/main/java/com/safetypin/authentication/dto/AuthResponse.java @@ -0,0 +1,13 @@ +package com.safetypin.authentication.dto; + +import lombok.*; + +@Data +@Getter +@Setter +@AllArgsConstructor +public class AuthResponse { + private boolean success; + private String message; + private Object data; +} \ No newline at end of file -- GitLab From 616c0b6b1ae898af37b6477c456b21049144d8b2 Mon Sep 17 00:00:00 2001 From: riorio805 <sefrianojieftara@gmail.com> Date: Wed, 26 Feb 2025 17:46:32 +0700 Subject: [PATCH 37/70] [REFACTOR] Replace multiple duplicated strings with a constant variable Prevent change propagation by only changing in a single place to increase maintainability --- .../authentication/service/AuthenticationService.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java index 5b92625..0af2bed 100644 --- a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java +++ b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java @@ -15,6 +15,7 @@ import java.time.Period; @Service public class AuthenticationService { + private static final String EMAIL_PROVIDER = "EMAIL"; private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; @@ -37,7 +38,7 @@ public class AuthenticationService { } String encodedPassword = passwordEncoder.encode(request.getPassword()); User user = new User(request.getEmail(), encodedPassword, request.getName(), false, "USER", - request.getBirthdate(), "EMAIL", null); + request.getBirthdate(), EMAIL_PROVIDER, null); user = userRepository.save(user); otpService.generateOTP(request.getEmail()); logger.info("OTP generated for {} at {}", request.getEmail(), java.time.LocalDateTime.now()); @@ -51,7 +52,7 @@ public class AuthenticationService { } User existing = userRepository.findByEmail(request.getEmail()); if (existing != null) { - if ("EMAIL".equals(existing.getProvider())) { + if (EMAIL_PROVIDER.equals(existing.getProvider())) { throw new IllegalArgumentException("An account with this email exists. Please sign in using your email and password."); } return existing; @@ -109,7 +110,7 @@ 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".equals(user.getProvider())) { + if (user == null || !EMAIL_PROVIDER.equals(user.getProvider())) { throw new IllegalArgumentException("Password reset is only available for email-registered users."); } // In production, send a reset token via email. -- GitLab From 99c1b2156368f7bc936f9d6f7e648bfbc14d7251 Mon Sep 17 00:00:00 2001 From: riorio805 <sefrianojieftara@gmail.com> Date: Wed, 26 Feb 2025 17:51:01 +0700 Subject: [PATCH 38/70] [REFACTOR] Use argument passed to example method sonarqube is more happy :)) --- .../safetypin/authentication/service/AuthenticationService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java index 0af2bed..dce7ad4 100644 --- a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java +++ b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java @@ -126,6 +126,7 @@ public class AuthenticationService { if (!user.isVerified()) { return "Your account is not verified. Please complete OTP verification. You may request a new OTP after 2 minutes."; } + logger.info("AuthenticationService.postContent :: Content posted: {}", content); // For demo purposes, we assume the post is successful. return "Content posted successfully"; } -- GitLab From 5889deb8f622ad2d74d9411a663181dd6887f186 Mon Sep 17 00:00:00 2001 From: riorio805 <sefrianojieftara@gmail.com> Date: Wed, 26 Feb 2025 17:55:47 +0700 Subject: [PATCH 39/70] [REFACTOR] change registerEmail+registerSocial to handle thrown exceptions and return ResponseEntity<AuthResponse> instead --- .../controller/AuthenticationController.java | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java b/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java index 08f921c..d7e04c0 100644 --- a/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java +++ b/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java @@ -1,9 +1,11 @@ package com.safetypin.authentication.controller; +import com.safetypin.authentication.dto.AuthResponse; import com.safetypin.authentication.dto.PasswordResetRequest; import com.safetypin.authentication.dto.RegistrationRequest; import com.safetypin.authentication.dto.SocialLoginRequest; import com.safetypin.authentication.exception.InvalidCredentialsException; +import com.safetypin.authentication.exception.UserAlreadyExistsException; import com.safetypin.authentication.model.User; import com.safetypin.authentication.service.AuthenticationService; import org.springframework.http.HttpStatus; @@ -24,14 +26,28 @@ public class AuthenticationController { // Endpoint for email registration @PostMapping("/register-email") - public User registerEmail(@Valid @RequestBody RegistrationRequest request) { - return authenticationService.registerUser(request); + public ResponseEntity<AuthResponse> registerEmail(@Valid @RequestBody RegistrationRequest request) { + User user; + try { + user = authenticationService.registerUser(request); + } catch (IllegalArgumentException | UserAlreadyExistsException e) { + AuthResponse response = new AuthResponse(false, e.getMessage(), null); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); + } + return ResponseEntity.ok().body(new AuthResponse(true, "OK", user)); } // Endpoint for social registration/login @PostMapping("/register-social") - public User registerSocial(@Valid @RequestBody SocialLoginRequest request) { - return authenticationService.socialLogin(request); + public ResponseEntity<AuthResponse> registerSocial(@Valid @RequestBody SocialLoginRequest request) { + User user; + try { + user = authenticationService.socialLogin(request); + } catch (IllegalArgumentException | UserAlreadyExistsException e) { + AuthResponse response = new AuthResponse(false, e.getMessage(), null); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); + } + return ResponseEntity.ok().body(new AuthResponse(true, "OK", user)); } // OTP verification endpoint @@ -45,7 +61,7 @@ public class AuthenticationController { // Endpoint for email login @PostMapping("/login-email") - public ResponseEntity<?> loginEmail(@RequestParam String email, @RequestParam String password) { + public ResponseEntity<Object> loginEmail(@RequestParam String email, @RequestParam String password) { try { return ResponseEntity.ok(authenticationService.loginUser(email, password)); } catch (InvalidCredentialsException e){ -- GitLab From 46aadd7728e46d95bbadade590499e585b2613ff Mon Sep 17 00:00:00 2001 From: riorio805 <sefrianojieftara@gmail.com> Date: Wed, 26 Feb 2025 17:56:31 +0700 Subject: [PATCH 40/70] [REFACTOR] change exception type to better reflect what errored --- .../safetypin/authentication/service/AuthenticationService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java index dce7ad4..96bca07 100644 --- a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java +++ b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java @@ -53,7 +53,7 @@ public class AuthenticationService { User existing = userRepository.findByEmail(request.getEmail()); if (existing != null) { if (EMAIL_PROVIDER.equals(existing.getProvider())) { - throw new IllegalArgumentException("An account with this email exists. Please sign in using your email and password."); + throw new UserAlreadyExistsException("An account with this email exists. Please sign in using your email and password."); } return existing; } -- GitLab From 31ea98b5ab051f7ab39cbfa5bce9bd8130e7c003 Mon Sep 17 00:00:00 2001 From: Fredo <fredotanzil@gmail.com> Date: Wed, 26 Feb 2025 18:40:21 +0700 Subject: [PATCH 41/70] [GREEN] Fix DevDataSeeder not working --- .../safetypin/authentication/seeder/DevDataSeeder.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java b/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java index e33c4f6..58dc982 100644 --- a/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java +++ b/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java @@ -2,10 +2,15 @@ package com.safetypin.authentication.seeder; import com.safetypin.authentication.model.User; import com.safetypin.authentication.repository.UserRepository; +import jakarta.annotation.PostConstruct; +import org.springframework.context.annotation.Profile; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Component; import java.time.LocalDate; +@Component +@Profile({"dev"}) public class DevDataSeeder implements Runnable { private final UserRepository userRepository; @@ -16,6 +21,11 @@ public class DevDataSeeder implements Runnable { this.passwordEncoder = passwordEncoder; } + @PostConstruct + public void init() { + run(); + } + @Override public void run() { // Only seed if there are no users in the repository -- GitLab From 5d212604274f1f3e857d52e9ee2cf4f5fe25ce21 Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Wed, 26 Feb 2025 20:16:00 +0700 Subject: [PATCH 42/70] [REFACTOR] AuthenticationControllerTest to disable CSRF and update JSON path assertions --- .../controller/AuthenticationControllerTest.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java b/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java index 2908d9a..2051cf1 100644 --- a/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java +++ b/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java @@ -15,6 +15,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import org.springframework.http.MediaType; import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.web.SecurityFilterChain; import org.springframework.test.web.servlet.MockMvc; @@ -51,11 +52,13 @@ class AuthenticationControllerTest { static class TestSecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http.authorizeHttpRequests(authorize -> authorize.anyRequest().permitAll()); - return http.build(); + http.csrf(AbstractHttpConfigurer::disable) + .authorizeHttpRequests(authorize -> authorize.anyRequest().permitAll()); + return http.build(); } } + @Test void testRegisterEmail() throws Exception { RegistrationRequest request = new RegistrationRequest(); @@ -73,8 +76,8 @@ class AuthenticationControllerTest { .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isOk()) - .andExpect(jsonPath("$.id").value(1L)) - .andExpect(jsonPath("$.email").value("email@example.com")); + .andExpect(jsonPath("$.data.id").value(1L)) + .andExpect(jsonPath("$.data.email").value("email@example.com")); } @Test @@ -96,8 +99,8 @@ class AuthenticationControllerTest { .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isOk()) - .andExpect(jsonPath("$.id").value(2L)) - .andExpect(jsonPath("$.email").value("social@example.com")); + .andExpect(jsonPath("$.data.id").value(2L)) + .andExpect(jsonPath("$.data.email").value("social@example.com")); } @Test -- GitLab From 19277c810dacf0a96db1b9b5555574e78225e322 Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Wed, 26 Feb 2025 20:16:08 +0700 Subject: [PATCH 43/70] [REFACTOR] Update exception type in AuthenticationServiceTest for social login --- .../authentication/service/AuthenticationServiceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java b/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java index bdf05fe..b1f12b9 100644 --- a/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java +++ b/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java @@ -121,7 +121,7 @@ class AuthenticationServiceTest { LocalDate.now().minusYears(30), "EMAIL", null); when(userRepository.findByEmail("social@example.com")).thenReturn(existingUser); - Exception exception = assertThrows(IllegalArgumentException.class, () -> + Exception exception = assertThrows(UserAlreadyExistsException.class, () -> authenticationService.socialLogin(request) ); assertTrue(exception.getMessage().contains("An account with this email exists")); -- GitLab From a0ec570d468c6c022dfeca39fbbf623a56d89f5b Mon Sep 17 00:00:00 2001 From: Muhammad Raihan Akbar <ianakbar711@gmail.com> Date: Wed, 26 Feb 2025 21:34:40 +0700 Subject: [PATCH 44/70] [REFACTOR] changed app dev db properties to localhost --- .env | 3 --- src/main/resources/application-dev.properties | 6 +++--- 2 files changed, 3 insertions(+), 6 deletions(-) delete mode 100644 .env diff --git a/.env b/.env deleted file mode 100644 index 6d5f502..0000000 --- a/.env +++ /dev/null @@ -1,3 +0,0 @@ -DB_URL=jdbc:postgresql://localhost:5432/be-authentication -DB_USERNAME=postgres -DB_PASSWORD=postgres diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties index a0f2b58..b7c904d 100644 --- a/src/main/resources/application-dev.properties +++ b/src/main/resources/application-dev.properties @@ -1,8 +1,8 @@ spring.application.name=authentication -spring.datasource.url=${DB_URL} -spring.datasource.username=${DB_PASSWORD} -spring.datasource.password=${DB_PASSWORD} +spring.datasource.url=jdbc:postgresql://localhost:5432/be-authentication +spring.datasource.username=postgres +spring.datasource.password=postgres spring.datasource.driver-class-name=org.postgresql.Driver spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect -- GitLab From 35e8c162a9ade451791863fe98cdfe365c0da8db Mon Sep 17 00:00:00 2001 From: Muhammad Raihan Akbar <ianakbar711@gmail.com> Date: Wed, 26 Feb 2025 22:23:15 +0700 Subject: [PATCH 45/70] [REFACTOR] edited app properties for active profile setup --- src/main/resources/application.properties | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index c12917a..7087d05 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,2 @@ -spring.application.name=authentication \ No newline at end of file +spring.application.name=authentication +spring.profiles.active=${PRODUCTION:dev} \ No newline at end of file -- GitLab From 09e7c72125b87dddba95dc4ada16ef4442ff8dbb Mon Sep 17 00:00:00 2001 From: Muhammad Raihan Akbar <ianakbar711@gmail.com> Date: Wed, 26 Feb 2025 22:29:29 +0700 Subject: [PATCH 46/70] [REFACTOR] configured env and staging properties --- Dockerfile | 18 ++++++++++++++++++ .../resources/application-staging.properties | 13 ++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index a5403ef..9372156 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,6 +6,24 @@ RUN mvn clean package -DskipTests # Step 2: Use OpenJDK 21 to run the application FROM openjdk:21-jdk-slim + +# Setup envs +ARG PRODUCTION +ARG JDBC_DATABASE_PASSWORD +ARG JDBC_DATABASE_URL +ARG JDBC_DATABASE_USERNAME +ARG JDBC_STAGING_DATABASE_USERNAME +ARG JDBC_STAGING_DATABASE_URL +ARG JDBC_STAGING_DATABASE_URL + +ENV PRODUCTION ${PRODUCTION} +ENV JDBC_DATABASE_PASSWORD ${JDBC_DATABASE_PASSWORD} +ENV JDBC_DATABASE_URL ${JDBC_DATABASE_URL} +ENV JDBC_DATABASE_USERNAME ${JDBC_DATABASE_USERNAME} +ENV JDBC_STAGING_DATABASE_PASSWORD ${JDBC_STAGING_DATABASE_PASSWORD} +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 EXPOSE 8080 diff --git a/src/main/resources/application-staging.properties b/src/main/resources/application-staging.properties index c12917a..a0f2b58 100644 --- a/src/main/resources/application-staging.properties +++ b/src/main/resources/application-staging.properties @@ -1 +1,12 @@ -spring.application.name=authentication \ No newline at end of file +spring.application.name=authentication + +spring.datasource.url=${DB_URL} +spring.datasource.username=${DB_PASSWORD} +spring.datasource.password=${DB_PASSWORD} + +spring.datasource.driver-class-name=org.postgresql.Driver +spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect + +# Hibernate Properties +spring.jpa.hibernate.ddl-auto=update +spring.jpa.show-sql=true \ No newline at end of file -- GitLab From 0d88a0df36d481d6b683185aa2a604ec42dca10c Mon Sep 17 00:00:00 2001 From: Muhammad Raihan Akbar <ianakbar711@gmail.com> Date: Wed, 26 Feb 2025 22:34:24 +0700 Subject: [PATCH 47/70] [REFACTOR] reconfigured the db variables for staging properties --- src/main/resources/application-staging.properties | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/resources/application-staging.properties b/src/main/resources/application-staging.properties index a0f2b58..8491638 100644 --- a/src/main/resources/application-staging.properties +++ b/src/main/resources/application-staging.properties @@ -1,8 +1,8 @@ spring.application.name=authentication -spring.datasource.url=${DB_URL} -spring.datasource.username=${DB_PASSWORD} -spring.datasource.password=${DB_PASSWORD} +spring.datasource.url=${JDBC_STAGING_DATABASE_URL} +spring.datasource.username=${JDBC_STAGING_DATABASE_USERNAME} +spring.datasource.password=${JDBC_STAGING_DATABASE_PASSWORD} spring.datasource.driver-class-name=org.postgresql.Driver spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect -- GitLab From 94d37fa4e84c28b8fc9bfe4de4cafd18c975aa33 Mon Sep 17 00:00:00 2001 From: Muhammad Raihan Akbar <ianakbar711@gmail.com> Date: Wed, 26 Feb 2025 22:42:18 +0700 Subject: [PATCH 48/70] [REFACTOR] added jacoco plugin for test coverages --- pom.xml | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/pom.xml b/pom.xml index a35f43c..feeca04 100644 --- a/pom.xml +++ b/pom.xml @@ -155,16 +155,6 @@ <scope>test</scope> </dependency> - - <dependency> - <groupId>org.jacoco</groupId> - <artifactId>org.jacoco.agent</artifactId> - <version>0.8.11</version> - <scope>test</scope> - </dependency> - - - </dependencies> <build> @@ -212,6 +202,25 @@ <artifactId>maven-jar-plugin</artifactId> <version>3.2.0</version> </plugin> + <plugin> + <groupId>org.jacoco</groupId> + <artifactId>jacoco-maven-plugin</artifactId> + <version>0.8.12</version> + <executions> + <execution> + <goals> + <goal>prepare-agent</goal> + </goals> + </execution> + <execution> + <id>report</id> + <phase>test</phase> + <goals> + <goal>report</goal> + </goals> + </execution> + </executions> + </plugin> </plugins> </build> -- GitLab From 01197d8108c7689c46bde78fcbad15272cf8e2b8 Mon Sep 17 00:00:00 2001 From: Muhammad Raihan Akbar <ianakbar711@gmail.com> Date: Wed, 26 Feb 2025 22:44:30 +0700 Subject: [PATCH 49/70] [REFACTOR] added continuous deployment for staging environment --- .github/workflows/staging-ci-cd.yml | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/.github/workflows/staging-ci-cd.yml b/.github/workflows/staging-ci-cd.yml index ccc2d24..e69f52a 100644 --- a/.github/workflows/staging-ci-cd.yml +++ b/.github/workflows/staging-ci-cd.yml @@ -40,16 +40,34 @@ jobs: uses: actions/checkout@v3 - name: Install the gcloud CLI - uses: google-github-actions/setup-gcloud@v0 + uses: google-github-actions/setup-gcloud@v2 with: project_id: ${{ secrets.GOOGLE_PROJECT }} service_account_key: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} export_default_credentials: true + - name: Authenticate with GCP + uses: google-github-actions/auth@v1 + with: + credentials_json: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} + - name: Build and Push Docker Image env: GOOGLE_PROJECT: ${{ secrets.GOOGLE_PROJECT }} run: | gcloud auth configure-docker us-central1-docker.pkg.dev - docker build -t us-central1-docker.pkg.dev/$GOOGLE_PROJECT/my-repository/authentication:latest . - docker push us-central1-docker.pkg.dev/$GOOGLE_PROJECT/my-repository/authentication:latest \ No newline at end of file + docker build -t us-central1-docker.pkg.dev/$GOOGLE_PROJECT/staging-repository/authentication:latest . + docker push us-central1-docker.pkg.dev/$GOOGLE_PROJECT/staging-repository/authentication:latest + + - name: Install required components + run: | + gcloud components update + gcloud components install gke-gcloud-auth-plugin + + - name: Deploy to GKE + env: + GOOGLE_PROJECT: ${{ secrets.GOOGLE_PROJECT }} + run: | + gcloud container clusters get-credentials safetypin-staging --region asia-southeast2 + sed -i "s/GOOGLE_PROJECT/$GOOGLE_PROJECT/g" resources.yaml + kubectl apply -f resources.yaml \ No newline at end of file -- GitLab From 761fae8719e89af6d1b0a137e9a6144f14448fe1 Mon Sep 17 00:00:00 2001 From: Muhammad Raihan Akbar <ianakbar711@gmail.com> Date: Wed, 26 Feb 2025 23:11:07 +0700 Subject: [PATCH 50/70] [REFACTOR] added build args for docker in staging environment --- .github/workflows/staging-ci-cd.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/staging-ci-cd.yml b/.github/workflows/staging-ci-cd.yml index e69f52a..f64e83a 100644 --- a/.github/workflows/staging-ci-cd.yml +++ b/.github/workflows/staging-ci-cd.yml @@ -53,10 +53,14 @@ jobs: - name: Build and Push Docker Image env: + PRODUCTION: staging GOOGLE_PROJECT: ${{ secrets.GOOGLE_PROJECT }} + JDBC_STAGING_DATABASE_PASSWORD: ${{ secrets.JDBC_STAGING_DATABASE_PASSWORD }} + JDBC_STAGING_DATABASE_URL: ${{ secrets.JDBC_STAGING_DATABASE_URL }} + JDBC_STAGING_DATABASE_USERNAME: ${{ secrets.JDBC_STAGING_DATABASE_USERNAME }} run: | gcloud auth configure-docker us-central1-docker.pkg.dev - docker build -t us-central1-docker.pkg.dev/$GOOGLE_PROJECT/staging-repository/authentication:latest . + docker build --build-arg PRODUCTION=$PRODUCTION --build-arg JDBC_STAGING_DATABASE_PASSWORD=$JDBC_STAGING_DATABASE_PASSWORD --build-arg JDBC_STAGING_DATABASE_URL=$JDBC_STAGING_DATABASE_URL --build-arg JDBC_STAGING_DATABASE_USERNAME=$JDBC_STAGING_DATABASE_USERNAME -t us-central1-docker.pkg.dev/$GOOGLE_PROJECT/staging-repository/authentication:latest . docker push us-central1-docker.pkg.dev/$GOOGLE_PROJECT/staging-repository/authentication:latest - name: Install required components -- GitLab From 9904cf2ea61ca9f9d0ab9bede289aec838576e62 Mon Sep 17 00:00:00 2001 From: Muhammad Raihan Akbar <ianakbar711@gmail.com> Date: Wed, 26 Feb 2025 23:19:20 +0700 Subject: [PATCH 51/70] [REFACTOR] changed the deployment repository for staging --- .github/workflows/staging-ci-cd.yml | 1 + resources.yaml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/staging-ci-cd.yml b/.github/workflows/staging-ci-cd.yml index f64e83a..0c52350 100644 --- a/.github/workflows/staging-ci-cd.yml +++ b/.github/workflows/staging-ci-cd.yml @@ -71,6 +71,7 @@ jobs: - name: Deploy to GKE env: GOOGLE_PROJECT: ${{ secrets.GOOGLE_PROJECT }} + GOOGLE_REPOSiTORY: staging-repository run: | gcloud container clusters get-credentials safetypin-staging --region asia-southeast2 sed -i "s/GOOGLE_PROJECT/$GOOGLE_PROJECT/g" resources.yaml diff --git a/resources.yaml b/resources.yaml index a3d64c5..f9611f7 100644 --- a/resources.yaml +++ b/resources.yaml @@ -29,6 +29,6 @@ spec: spec: containers: - name: authentication - image: us-central1-docker.pkg.dev/GOOGLE_PROJECT/my-repository/authentication:latest + image: us-central1-docker.pkg.dev/GOOGLE_PROJECT/GOOGLE_REPOSITORY/authentication:latest ports: - containerPort: 8080 \ No newline at end of file -- GitLab From b0eb787f33249e2f4f7bc897c9e499be52b5e698 Mon Sep 17 00:00:00 2001 From: Muhammad Raihan Akbar <ianakbar711@gmail.com> Date: Wed, 26 Feb 2025 23:34:20 +0700 Subject: [PATCH 52/70] [REFACTOR] reconfigured yaml file to differentiate prod and staging --- .github/workflows/staging-ci-cd.yml | 4 ++-- resources.yaml => production.yaml | 2 +- staging.yaml | 34 +++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 3 deletions(-) rename resources.yaml => production.yaml (84%) create mode 100644 staging.yaml diff --git a/.github/workflows/staging-ci-cd.yml b/.github/workflows/staging-ci-cd.yml index 0c52350..b9f2245 100644 --- a/.github/workflows/staging-ci-cd.yml +++ b/.github/workflows/staging-ci-cd.yml @@ -74,5 +74,5 @@ jobs: GOOGLE_REPOSiTORY: staging-repository run: | gcloud container clusters get-credentials safetypin-staging --region asia-southeast2 - sed -i "s/GOOGLE_PROJECT/$GOOGLE_PROJECT/g" resources.yaml - kubectl apply -f resources.yaml \ No newline at end of file + sed -i "s/GOOGLE_PROJECT/$GOOGLE_PROJECT/g" staging.yaml + kubectl apply -f staging.yaml \ No newline at end of file diff --git a/resources.yaml b/production.yaml similarity index 84% rename from resources.yaml rename to production.yaml index f9611f7..a3d64c5 100644 --- a/resources.yaml +++ b/production.yaml @@ -29,6 +29,6 @@ spec: spec: containers: - name: authentication - image: us-central1-docker.pkg.dev/GOOGLE_PROJECT/GOOGLE_REPOSITORY/authentication:latest + image: us-central1-docker.pkg.dev/GOOGLE_PROJECT/my-repository/authentication:latest ports: - containerPort: 8080 \ No newline at end of file diff --git a/staging.yaml b/staging.yaml new file mode 100644 index 0000000..91457e7 --- /dev/null +++ b/staging.yaml @@ -0,0 +1,34 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: authentication +spec: + type: LoadBalancer + selector: + app: authentication + ports: + - port: 80 + targetPort: 8080 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: authentication + labels: + app: authentication +spec: + replicas: 1 + selector: + matchLabels: + app: authentication + template: + metadata: + labels: + app: authentication + spec: + containers: + - name: authentication + image: us-central1-docker.pkg.dev/GOOGLE_PROJECT/staging-repository/authentication:latest + ports: + - containerPort: 8080 \ No newline at end of file -- GitLab From 13ac5df5ee4a73d2d94ad170c6fbc754570486f7 Mon Sep 17 00:00:00 2001 From: Fredo <fredotanzil@gmail.com> Date: Thu, 27 Feb 2025 12:28:37 +0700 Subject: [PATCH 53/70] handle fail login email --- .../controller/AuthenticationController.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java b/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java index d7e04c0..ee3bcfc 100644 --- a/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java +++ b/src/main/java/com/safetypin/authentication/controller/AuthenticationController.java @@ -65,16 +65,22 @@ public class AuthenticationController { try { return ResponseEntity.ok(authenticationService.loginUser(email, password)); } catch (InvalidCredentialsException e){ - return ResponseEntity.status(HttpStatus.UNAUTHORIZED) - .body(e.getMessage()); + AuthResponse response = new AuthResponse(false, e.getMessage(), null); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); } } // Endpoint for social login @PostMapping("/login-social") - public User loginSocial(@RequestParam String email) { - return authenticationService.loginSocial(email); + public ResponseEntity<Object> loginSocial(@RequestParam String email) { + try { + return ResponseEntity.ok(authenticationService.loginSocial(email)); + } catch (InvalidCredentialsException e){ + AuthResponse response = new AuthResponse(false, e.getMessage(), null); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); + } + } -- GitLab From 125e7f7f6c621de4554216848fd3f3b4984ef02c Mon Sep 17 00:00:00 2001 From: riorio805 <sefrianojieftara@gmail.com> Date: Fri, 28 Feb 2025 16:39:09 +0700 Subject: [PATCH 54/70] [REFACTOR] Remove duplicated string in seeding with existing string constant --- .../authentication/seeder/DevDataSeeder.java | 13 ++++++++----- .../service/AuthenticationService.java | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java b/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java index 58dc982..4f23a81 100644 --- a/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java +++ b/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java @@ -2,6 +2,9 @@ package com.safetypin.authentication.seeder; import com.safetypin.authentication.model.User; import com.safetypin.authentication.repository.UserRepository; +import static com.safetypin.authentication.service.AuthenticationService.EMAIL_PROVIDER; + +import com.safetypin.authentication.service.AuthenticationService; import jakarta.annotation.PostConstruct; import org.springframework.context.annotation.Profile; import org.springframework.security.crypto.password.PasswordEncoder; @@ -36,7 +39,7 @@ public class DevDataSeeder implements Runnable { true, "user", LocalDate.of(1990, 1, 1), - "EMAIL", + EMAIL_PROVIDER, "social1")); userRepository.save(new User("user2@example.com", @@ -45,7 +48,7 @@ public class DevDataSeeder implements Runnable { true, "user", LocalDate.of(1991, 2, 2), - "EMAIL", + EMAIL_PROVIDER, "social2")); userRepository.save(new User("user3@example.com", @@ -54,7 +57,7 @@ public class DevDataSeeder implements Runnable { true, "user", LocalDate.of(1992, 3, 3), - "EMAIL", + EMAIL_PROVIDER, "social3")); userRepository.save(new User("user4@example.com", @@ -63,7 +66,7 @@ public class DevDataSeeder implements Runnable { true, "user", LocalDate.of(1993, 4, 4), - "EMAIL", + EMAIL_PROVIDER, "social4")); userRepository.save(new User("user5@example.com", @@ -72,7 +75,7 @@ public class DevDataSeeder implements Runnable { true, "user", LocalDate.of(1994, 5, 5), - "EMAIL", + EMAIL_PROVIDER, "social5")); } } diff --git a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java index 96bca07..3a177cc 100644 --- a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java +++ b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java @@ -15,7 +15,7 @@ import java.time.Period; @Service public class AuthenticationService { - private static final String EMAIL_PROVIDER = "EMAIL"; + public static final String EMAIL_PROVIDER = "EMAIL"; private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; -- GitLab From f952c7c610e7898f601d59d18f0c6a333809c1c0 Mon Sep 17 00:00:00 2001 From: riorio805 <sefrianojieftara@gmail.com> Date: Fri, 28 Feb 2025 17:21:05 +0700 Subject: [PATCH 55/70] Fix compile errors in IntelliJ by adding version to lombok in pom.xml --- pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pom.xml b/pom.xml index feeca04..bfbd19b 100644 --- a/pom.xml +++ b/pom.xml @@ -85,6 +85,11 @@ <scope>runtime</scope> </dependency> + <dependency> + <groupId>net.datafaker</groupId> + <artifactId>datafaker</artifactId> + <version>2.4.2</version> + </dependency> <!-- REST-assured for integration testing --> <dependency> @@ -171,6 +176,7 @@ <path> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> + <version>1.18.36</version> </path> </annotationProcessorPaths> </configuration> -- GitLab From 62dbeb76defcf6fee9608e78881ee6ced421a705 Mon Sep 17 00:00:00 2001 From: riorio805 <sefrianojieftara@gmail.com> Date: Fri, 28 Feb 2025 17:59:44 +0700 Subject: [PATCH 56/70] [REFACTOR] Remove long constructor in User and replace all occurrences with the no-arg constructor and set attribute. --- .../safetypin/authentication/model/User.java | 37 ++--- .../authentication/seeder/DevDataSeeder.java | 92 ++++++----- .../service/AuthenticationService.java | 24 ++- .../AuthenticationControllerTest.java | 44 ++++- .../authentication/model/UserTest.java | 11 +- .../seeder/DevDataSeederTest.java | 18 +- .../service/AuthenticationServiceTest.java | 156 +++++++++++++++--- 7 files changed, 268 insertions(+), 114 deletions(-) diff --git a/src/main/java/com/safetypin/authentication/model/User.java b/src/main/java/com/safetypin/authentication/model/User.java index c08cb81..d38fe7d 100644 --- a/src/main/java/com/safetypin/authentication/model/User.java +++ b/src/main/java/com/safetypin/authentication/model/User.java @@ -10,59 +10,42 @@ import java.time.LocalDate; @Table(name = "users") public class User { - @Setter - @Getter @Id + @Setter @Getter @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Setter - @Getter + @Setter @Getter @Column(nullable = false, unique = true) private String email; // May be null for social login users - @Setter - @Getter + @Setter @Getter @Column(nullable = false) private String password; - @Setter - @Getter + @Setter @Getter @Column(nullable = false) private String name; @Column(nullable = false) private boolean isVerified = false; - @Setter - @Getter + @Setter @Getter private String role; // New fields - @Setter - @Getter + @Setter @Getter private LocalDate birthdate; - @Setter - @Getter + + @Setter @Getter private String provider; // "EMAIL", "GOOGLE", "APPLE" - @Setter - @Getter + + @Setter @Getter private String socialId; // For social login users public User() {} - public User(String email, String password, String name, boolean isVerified, String role, - LocalDate birthdate, String provider, String socialId) { - this.email = email; - this.password = password; - this.name = name; - this.isVerified = isVerified; - this.role = role; - this.birthdate = birthdate; - this.provider = provider; - this.socialId = socialId; - } // Getters and setters diff --git a/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java b/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java index 4f23a81..7b9a5b2 100644 --- a/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java +++ b/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java @@ -33,50 +33,62 @@ public class DevDataSeeder implements Runnable { public void run() { // Only seed if there are no users in the repository if (userRepository.count() == 0) { - userRepository.save(new User("user1@example.com", - passwordEncoder.encode("password1"), - "User One", - true, - "user", - LocalDate.of(1990, 1, 1), - EMAIL_PROVIDER, - "social1")); - userRepository.save(new User("user2@example.com", - passwordEncoder.encode("password2"), - "User Two", - true, - "user", - LocalDate.of(1991, 2, 2), - EMAIL_PROVIDER, - "social2")); + User user1 = new User(); + user1.setEmail("user1@example.com"); + user1.setPassword(passwordEncoder.encode("password1")); + user1.setName("User One"); + user1.setVerified(true); + user1.setRole("user"); + user1.setBirthdate(LocalDate.of(1990, 1, 1)); + user1.setProvider(EMAIL_PROVIDER); + user1.setSocialId("social1"); + userRepository.save(user1); - userRepository.save(new User("user3@example.com", - passwordEncoder.encode("password3"), - "User Three", - true, - "user", - LocalDate.of(1992, 3, 3), - EMAIL_PROVIDER, - "social3")); + User user2 = new User(); + user2.setEmail("user2@example.com"); + user2.setPassword(passwordEncoder.encode("password2")); + user2.setName("User Two"); + user2.setVerified(true); + user2.setRole("user"); + user2.setBirthdate(LocalDate.of(1991, 2, 2)); + user2.setProvider(EMAIL_PROVIDER); + user2.setSocialId("social2"); + userRepository.save(user2); - userRepository.save(new User("user4@example.com", - passwordEncoder.encode("password4"), - "User Four", - true, - "user", - LocalDate.of(1993, 4, 4), - EMAIL_PROVIDER, - "social4")); - userRepository.save(new User("user5@example.com", - passwordEncoder.encode("password5"), - "User Five", - true, - "user", - LocalDate.of(1994, 5, 5), - EMAIL_PROVIDER, - "social5")); + User user3 = new User(); + user3.setEmail("user3@example.com"); + user3.setPassword(passwordEncoder.encode("password3")); + user3.setName("User Three"); + user3.setVerified(true); + user3.setRole("user"); + user3.setBirthdate(LocalDate.of(1992, 3, 3)); + user3.setProvider(EMAIL_PROVIDER); + user3.setSocialId("social3"); + userRepository.save(user3); + + User user4 = new User(); + user4.setEmail("user4@example.com"); + user4.setPassword(passwordEncoder.encode("password4")); + user4.setName("User Four"); + user4.setVerified(true); + user4.setRole("user"); + user4.setBirthdate(LocalDate.of(1993, 4, 4)); + user4.setProvider(EMAIL_PROVIDER); + user4.setSocialId("social4"); + userRepository.save(user4); + + User user5 = new User(); + user5.setEmail("user5@example.com"); + user5.setPassword(passwordEncoder.encode("password5")); + user5.setName("User Five"); + user5.setVerified(true); + user5.setRole("user"); + user5.setBirthdate(LocalDate.of(1994, 5, 5)); + user5.setProvider(EMAIL_PROVIDER); + user5.setSocialId("social5"); + userRepository.save(user5); } } } diff --git a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java index 3a177cc..c5616f6 100644 --- a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java +++ b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java @@ -37,8 +37,16 @@ public class AuthenticationService { throw new UserAlreadyExistsException("User already exists with this email. If you registered using social login, please sign in with Google/Apple."); } String encodedPassword = passwordEncoder.encode(request.getPassword()); - User user = new User(request.getEmail(), encodedPassword, request.getName(), false, "USER", - request.getBirthdate(), EMAIL_PROVIDER, null); + + User user = new User(); + user.setEmail(request.getEmail()); + user.setPassword(encodedPassword); + user.setName(request.getName()); + user.setVerified(false); + user.setRole("USER"); + user.setBirthdate(request.getBirthdate()); + user.setProvider(EMAIL_PROVIDER); + user.setSocialId(null); user = userRepository.save(user); otpService.generateOTP(request.getEmail()); logger.info("OTP generated for {} at {}", request.getEmail(), java.time.LocalDateTime.now()); @@ -57,8 +65,16 @@ public class AuthenticationService { } return existing; } - User user = new User(request.getEmail(), null, request.getName(), true, "USER", - request.getBirthdate(), request.getProvider().toUpperCase(), request.getSocialId()); + User user = new User(); + user.setEmail(request.getEmail()); + user.setPassword(null); + user.setName(request.getName()); + user.setVerified(true); + user.setRole("USER"); + user.setBirthdate(request.getBirthdate()); + user.setProvider(request.getProvider().toUpperCase()); + user.setSocialId(request.getSocialId()); + user = userRepository.save(user); logger.info("User registered via {}: {} at {}", request.getProvider(), request.getEmail(), java.time.LocalDateTime.now()); return user; diff --git a/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java b/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java index 2051cf1..f0de3fd 100644 --- a/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java +++ b/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java @@ -67,8 +67,14 @@ class AuthenticationControllerTest { request.setName("Test User"); request.setBirthdate(LocalDate.now().minusYears(20)); - User user = new User("email@example.com", "encodedPassword", "Test User", false, "USER", - request.getBirthdate(), "EMAIL", null); + User user = new User(); + user.setEmail("email@example.com"); + user.setPassword("encodedPassword"); + user.setName("Test User"); + user.setRole("USER"); + user.setBirthdate(request.getBirthdate()); + user.setProvider("EMAIL"); + user.setId(1L); Mockito.when(authenticationService.registerUser(any(RegistrationRequest.class))).thenReturn(user); @@ -90,8 +96,15 @@ class AuthenticationControllerTest { request.setBirthdate(LocalDate.now().minusYears(25)); request.setSocialId("social123"); - User user = new User("social@example.com", null, "Social User", true, "USER", - request.getBirthdate(), "GOOGLE", "social123"); + User user = new User(); + user.setEmail("social@example.com"); + user.setPassword(null); + user.setName("Social User"); + user.setVerified(true); + user.setRole("USER"); + user.setBirthdate(request.getBirthdate()); + user.setProvider("GOOGLE"); + user.setSocialId("social123"); user.setId(2L); Mockito.when(authenticationService.socialLogin(any(SocialLoginRequest.class))).thenReturn(user); @@ -105,8 +118,16 @@ class AuthenticationControllerTest { @Test void testLoginEmail() throws Exception { - User user = new User("email@example.com", "encodedPassword", "Test User", true, "USER", - LocalDate.now().minusYears(20), "EMAIL", null); + User user = new User(); + user.setEmail("email@example.com"); + user.setPassword("encodedPassword"); + user.setName("Test User"); + user.setVerified(true); + user.setRole("USER"); + user.setBirthdate(LocalDate.now().minusYears(20)); + user.setProvider("EMAIL"); + user.setSocialId(null); + user.setId(1L); Mockito.when(authenticationService.loginUser(eq("email@example.com"), eq("password"))).thenReturn(user); @@ -120,8 +141,15 @@ class AuthenticationControllerTest { @Test void testLoginSocial() throws Exception { - User user = new User("social@example.com", null, "Social User", true, "USER", - LocalDate.now().minusYears(25), "GOOGLE", "social123"); + User user = new User(); + user.setEmail("social@example.com"); + user.setPassword(null); + user.setName("Social User"); + user.setVerified(true); + user.setRole("USER"); + user.setBirthdate(LocalDate.now().minusYears(25)); + user.setProvider("GOOGLE"); + user.setSocialId("social123"); user.setId(2L); Mockito.when(authenticationService.loginSocial(eq("social@example.com"))).thenReturn(user); diff --git a/src/test/java/com/safetypin/authentication/model/UserTest.java b/src/test/java/com/safetypin/authentication/model/UserTest.java index f66bde4..0b3c180 100644 --- a/src/test/java/com/safetypin/authentication/model/UserTest.java +++ b/src/test/java/com/safetypin/authentication/model/UserTest.java @@ -68,7 +68,16 @@ class UserTest { String provider = "EMAIL"; String socialId = null; - User user = new User(email, password, name, verified, role, birthdate, provider, socialId); + User user = new User(); + user.setEmail(email); + user.setPassword(password); + user.setName(name); + user.setVerified(verified); + user.setRole(role); + user.setBirthdate(birthdate); + user.setProvider(provider); + user.setSocialId(socialId); + // id remains null until set (by the persistence layer) assertNull(user.getId(), "Id should be null when not set"); diff --git a/src/test/java/com/safetypin/authentication/seeder/DevDataSeederTest.java b/src/test/java/com/safetypin/authentication/seeder/DevDataSeederTest.java index 8b58e96..04e4931 100644 --- a/src/test/java/com/safetypin/authentication/seeder/DevDataSeederTest.java +++ b/src/test/java/com/safetypin/authentication/seeder/DevDataSeederTest.java @@ -42,14 +42,16 @@ class DevDataSeederTest { @Test void testSeederDoesNotInsertIfUsersExist() { // Save an existing user into the repository - userRepository.save(new User("existing@example.com", - passwordEncoder.encode("test"), - "Existing User", - true, - "admin", - LocalDate.of(1990, 1, 1), - "EMAIL", - "social_9999")); + User user = new User(); + user.setEmail("existing@example.com"); + user.setPassword(passwordEncoder.encode("test")); + user.setName("Existing User"); + user.setVerified(true); + user.setRole("admin"); + user.setBirthdate(LocalDate.of(1990, 1, 1)); + user.setProvider("EMAIL"); + user.setSocialId("social_9999"); + userRepository.save(user); long countBefore = userRepository.count(); new DevDataSeeder(userRepository, passwordEncoder).run(); diff --git a/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java b/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java index b1f12b9..32b2265 100644 --- a/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java +++ b/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java @@ -77,8 +77,16 @@ class AuthenticationServiceTest { when(userRepository.findByEmail("test@example.com")).thenReturn(null); when(passwordEncoder.encode("password")).thenReturn("encodedPassword"); - User savedUser = new User("test@example.com", "encodedPassword", "Test User", false, "USER", - request.getBirthdate(), "EMAIL", null); + User savedUser = new User(); + savedUser.setEmail("test@example.com"); + savedUser.setPassword("encodedPassword"); + savedUser.setName("Test User"); + savedUser.setVerified(false); + savedUser.setRole("USER"); + savedUser.setBirthdate(request.getBirthdate()); + savedUser.setProvider("EMAIL"); + savedUser.setSocialId(null); + savedUser.setId(1L); when(userRepository.save(any(User.class))).thenReturn(savedUser); @@ -117,8 +125,16 @@ class AuthenticationServiceTest { request.setSocialId("social123"); request.setSocialToken("token"); - User existingUser = new User("social@example.com", "encodedPassword", "Existing User", false, "USER", - LocalDate.now().minusYears(30), "EMAIL", null); + User existingUser = new User(); + existingUser.setEmail("social@example.com"); + existingUser.setPassword("encodedPassword"); + existingUser.setName("Existing User"); + existingUser.setVerified(false); + existingUser.setRole("USER"); + existingUser.setBirthdate(LocalDate.now().minusYears(30)); + existingUser.setProvider("EMAIL"); + existingUser.setSocialId(null); + when(userRepository.findByEmail("social@example.com")).thenReturn(existingUser); Exception exception = assertThrows(UserAlreadyExistsException.class, () -> @@ -137,8 +153,16 @@ class AuthenticationServiceTest { request.setSocialId("social123"); request.setSocialToken("token"); - User existingUser = new User("social@example.com", null, "Social User", true, "USER", - LocalDate.now().minusYears(25), "GOOGLE", "social123"); + User existingUser = new User(); + existingUser.setEmail("social@example.com"); + existingUser.setPassword(null); + existingUser.setName("Social User"); + existingUser.setVerified(true); + existingUser.setRole("USER"); + existingUser.setBirthdate(LocalDate.now().minusYears(25)); + existingUser.setProvider("GOOGLE"); + existingUser.setSocialId("social123"); + when(userRepository.findByEmail("social@example.com")).thenReturn(existingUser); User result = authenticationService.socialLogin(request); @@ -157,8 +181,16 @@ class AuthenticationServiceTest { request.setSocialToken("token"); when(userRepository.findByEmail("social@example.com")).thenReturn(null); - User savedUser = new User("social@example.com", null, "Social User", true, "USER", - request.getBirthdate(), "GOOGLE", "social123"); + User savedUser = new User(); + savedUser.setEmail("social@example.com"); + savedUser.setPassword(null); + savedUser.setName("Social User"); + savedUser.setVerified(true); + savedUser.setRole("USER"); + savedUser.setBirthdate(request.getBirthdate()); + savedUser.setProvider("GOOGLE"); + savedUser.setSocialId("social123"); + savedUser.setId(2L); when(userRepository.save(any(User.class))).thenReturn(savedUser); @@ -180,8 +212,16 @@ class AuthenticationServiceTest { @Test void testLoginUser_InvalidPassword_NullPassword() { - User user = new User("test@example.com", null, "Test User", true, "USER", - LocalDate.now().minusYears(20), "EMAIL", null); + User user = new User(); + user.setEmail("test@example.com"); + user.setPassword(null); + user.setName("Test User"); + user.setVerified(true); + user.setRole("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, () -> @@ -192,8 +232,16 @@ class AuthenticationServiceTest { @Test void testLoginUser_InvalidPassword_WrongMatch() { - User user = new User("test@example.com", "encodedPassword", "Test User", true, "USER", - LocalDate.now().minusYears(20), "EMAIL", null); + User user = new User(); + user.setEmail("test@example.com"); + user.setPassword("encodedPassword"); + user.setName("Test User"); + user.setVerified(true); + user.setRole("USER"); + user.setBirthdate(LocalDate.now().minusYears(20)); + user.setProvider("EMAIL"); + user.setSocialId(null); + when(userRepository.findByEmail("test@example.com")).thenReturn(user); when(passwordEncoder.matches("wrongPassword", "encodedPassword")).thenReturn(false); @@ -205,8 +253,16 @@ class AuthenticationServiceTest { @Test void testLoginUser_Success() { - User user = new User("test@example.com", "encodedPassword", "Test User", true, "USER", - LocalDate.now().minusYears(20), "EMAIL", null); + User user = new User(); + user.setEmail("test@example.com"); + user.setPassword("encodedPassword"); + user.setName("Test User"); + user.setVerified(true); + user.setRole("USER"); + user.setBirthdate(LocalDate.now().minusYears(20)); + user.setProvider("EMAIL"); + user.setSocialId(null); + when(userRepository.findByEmail("test@example.com")).thenReturn(user); when(passwordEncoder.matches("password", "encodedPassword")).thenReturn(true); @@ -228,8 +284,16 @@ class AuthenticationServiceTest { @Test void testLoginSocial_Success() { - User user = new User("social@example.com", null, "Social User", true, "USER", - LocalDate.now().minusYears(25), "GOOGLE", "social123"); + User user = new User(); + user.setEmail("social@example.com"); + user.setPassword(null); + user.setName("Social User"); + user.setVerified(true); + user.setRole("USER"); + user.setBirthdate(LocalDate.now().minusYears(25)); + user.setProvider("GOOGLE"); + user.setSocialId("social123"); + when(userRepository.findByEmail("social@example.com")).thenReturn(user); User result = authenticationService.loginSocial("social@example.com"); @@ -243,8 +307,16 @@ 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("test@example.com", "encodedPassword", "Test User", false, "USER", - LocalDate.now().minusYears(20), "EMAIL", null); + User user = new User(); + user.setEmail("test@example.com"); + user.setPassword("encodedPassword"); + user.setName("Test User"); + user.setVerified(false); + user.setRole("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); @@ -277,8 +349,16 @@ class AuthenticationServiceTest { @Test void testForgotPassword_Success() { - User user = new User("test@example.com", "encodedPassword", "Test User", true, "USER", - LocalDate.now().minusYears(20), "EMAIL", null); + User user = new User(); + user.setEmail("test@example.com"); + user.setPassword("encodedPassword"); + user.setName("Test User"); + user.setVerified(true); + user.setRole("USER"); + user.setBirthdate(LocalDate.now().minusYears(20)); + user.setProvider("EMAIL"); + user.setSocialId(null); + when(userRepository.findByEmail("test@example.com")).thenReturn(user); assertDoesNotThrow(() -> authenticationService.forgotPassword("test@example.com")); @@ -294,8 +374,16 @@ class AuthenticationServiceTest { assertTrue(exception1.getMessage().contains("Password reset is only available for email-registered users.")); // Case 2: user exists but provider is not EMAIL - User user = new User("social@example.com", null, "Social User", true, "USER", - LocalDate.now().minusYears(25), "GOOGLE", "social123"); + User user = new User(); + user.setEmail("social@example.com"); + user.setPassword(null); + user.setName("Social User"); + user.setVerified(true); + user.setRole("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") @@ -314,8 +402,16 @@ class AuthenticationServiceTest { @Test void testPostContent_UserNotVerified() { - User user = new User("test@example.com", "encodedPassword", "Test User", false, "USER", - LocalDate.now().minusYears(20), "EMAIL", null); + User user = new User(); + user.setEmail("test@example.com"); + user.setPassword("encodedPassword"); + user.setName("Test User"); + user.setVerified(false); + user.setRole("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")); @@ -323,8 +419,16 @@ class AuthenticationServiceTest { @Test void testPostContent_UserVerified() { - User user = new User("test@example.com", "encodedPassword", "Test User", true, "USER", - LocalDate.now().minusYears(20), "EMAIL", null); + User user = new User(); + user.setEmail("test@example.com"); + user.setPassword("encodedPassword"); + user.setName("Test User"); + user.setVerified(true); + user.setRole("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); -- GitLab From 8b2b1716e1f298607f13d084281ead29fc8d3d8c Mon Sep 17 00:00:00 2001 From: riorio805 <sefrianojieftara@gmail.com> Date: Fri, 28 Feb 2025 18:02:19 +0700 Subject: [PATCH 57/70] [REFACTOR] Replace empty constructor in User with @NoArgsConstructor --- src/main/java/com/safetypin/authentication/model/User.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/safetypin/authentication/model/User.java b/src/main/java/com/safetypin/authentication/model/User.java index d38fe7d..41e776c 100644 --- a/src/main/java/com/safetypin/authentication/model/User.java +++ b/src/main/java/com/safetypin/authentication/model/User.java @@ -2,12 +2,14 @@ package com.safetypin.authentication.model; import jakarta.persistence.*; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; import java.time.LocalDate; @Entity @Table(name = "users") +@NoArgsConstructor public class User { @Id @@ -44,8 +46,6 @@ public class User { @Setter @Getter private String socialId; // For social login users - public User() {} - // Getters and setters -- GitLab From 75308f5b40f123fb3a7639f6b88aaede185f655c Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Fri, 28 Feb 2025 20:51:06 +0700 Subject: [PATCH 58/70] Add README.md with project overview, features, and installation instructions --- README.md | 180 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..10b7c73 --- /dev/null +++ b/README.md @@ -0,0 +1,180 @@ +# Authentication Microservice +[](https://sonarcloud.io/summary/new_code?id=safetypin-official_be-authentication) + +[](https://sonarcloud.io/summary/new_code?id=safetypin-official_be-authentication) + +[](https://sonarcloud.io/summary/new_code?id=safetypin-official_be-authentication) +[](https://sonarcloud.io/summary/new_code?id=safetypin-official_be-authentication) +[](https://sonarcloud.io/summary/new_code?id=safetypin-official_be-authentication) +[](https://sonarcloud.io/summary/new_code?id=safetypin-official_be-authentication) +[](https://sonarcloud.io/summary/new_code?id=safetypin-official_be-authentication) +[](https://sonarcloud.io/summary/new_code?id=safetypin-official_be-authentication) +[](https://sonarcloud.io/summary/new_code?id=safetypin-official_be-authentication) +[](https://sonarcloud.io/summary/new_code?id=safetypin-official_be-authentication) +[](https://sonarcloud.io/summary/new_code?id=safetypin-official_be-authentication) + +## Overview + +The **Authentication Microservice** is a Spring Boot-based REST API that handles user authentication and authorization. It supports both traditional email-based registration/login as well as social authentication (e.g., Google, Apple). The service includes features such as OTP (One-Time Password) verification, password reset simulation, and a simple content posting endpoint for verified users. + +## Table of Contents + +- [Features](#features) +- [Tech Stack](#tech-stack) +- [Prerequisites](#prerequisites) +- [Installation](#installation) +- [Configuration](#configuration) +- [API Endpoints](#api-endpoints) +- [Running Tests](#running-tests) +- [Development](#development) +- [Contributing](#contributing) +- [License](#license) +- [Acknowledgements](#acknowledgements) + +## Features + +- **Email Registration & Login:** Supports registration and login using email and password. +- **Social Authentication:** Simulated endpoints for registration and login using social providers. +- **OTP Verification:** Generates and verifies OTPs for user account validation. +- **Password Reset:** Simulated password reset functionality for email-based users. +- **Content Posting:** A secured endpoint that only allows verified users to post content. +- **Dev Data Seeder:** Automatically seeds development data when running under the `dev` profile. +- **Robust Testing:** Comprehensive unit and integration tests using JUnit 5, Spring Boot Test, and TestContainers. + +## Tech Stack + +- **Java:** 21 +- **Spring Boot:** 3.4.2 +- **Maven:** Build automation and dependency management +- **Spring Security:** For authentication and password encoding +- **Spring Data JPA:** For ORM and database access +- **PostgresSQL & H2:** Database support (PostgresSQL for production; H2 for development/testing) +- **JUnit 5 & TestContainers:** For testing and integration testing + +## Prerequisites + +- **Java 21** +- **Maven 3.6+** +- A running PostgresSQL instance (for production) or H2 (for development/testing) + +## Installation + +1. **Clone the repository:** +``` + git clone https://github.com/yourusername/authentication-microservice.git + cd authentication-microservice +``` +2. **Build the project using Maven:** +``` + mvn clean install +``` +3. **Run the application:** +``` + mvn spring-boot:run +``` + The service will start on the default port (typically 8080). + +## Configuration + +- **application.properties:** Configure your database, server port, and other environment-specific settings. +- **Profiles:** Use the `dev` profile for development. The `DevDataSeeder` will automatically seed sample user data when running under this profile: + +``` +mvn spring-boot:run -Dspring-boot.run.profiles=dev +``` + +## API Endpoints + +### Public Endpoints + +- **GET `/`** + Returns a simple "Hello, World!" greeting. + +### Authentication Endpoints + +- **POST `/api/auth/register-email`** + Registers a new user using email. + - **Request Body:** JSON containing `email`, `password`, `name`, and `birthdate`. + - **Response:** Success message with user data. + +- **POST `/api/auth/register-social`** + Registers or logs in a user using social authentication. + - **Request Body:** JSON containing `provider`, `socialToken`, `email`, `name`, `birthdate`, and `socialId`. + - **Response:** Success message with user data. + +- **POST `/api/auth/login-email`** + Authenticates a user using email and password. + - **Parameters:** `email`, `password`. + - **Response:** User data on successful login. + +- **POST `/api/auth/login-social`** + Logs in a user via social authentication. + - **Parameter:** `email`. + - **Response:** User data on successful social login. + +- **POST `/api/auth/verify-otp`** + Verifies the OTP for account validation. + - **Parameters:** `email`, `otp`. + - **Response:** Message indicating success or failure of OTP verification. + +- **POST `/api/auth/forgot-password`** + Simulates password reset for email-registered users. + - **Request Body:** JSON containing `email`. + - **Response:** Message indicating that reset instructions have been sent. + +- **POST `/api/auth/post`** + Allows posting of content for verified users. + - **Parameters:** `email`, `content`. + - **Response:** Success or failure message based on user verification. + +- **GET `/api/auth/dashboard`** + Returns dashboard data (currently a placeholder). + - **Response:** An empty JSON object. + +## Running Tests + +To run all unit and integration tests, execute: +``` +mvn test +``` + +Tests are written using JUnit 5 and cover controllers, services, repository interactions, and utility components such as OTP generation and validation. + +## Development + +- **Code Style:** The project adheres to standard Java coding conventions and uses Lombok to reduce boilerplate. +- **Continuous Integration:** Integration with CI tools is recommended. Test coverage is ensured using Maven Surefire and Failsafe plugins. +- **Debugging:** Utilize Spring Boot DevTools for hot-reloading during development. + +## Contributing + +Contributions are welcome! Please follow these steps: + +1. Fork the repository. +2. Create a new feature branch (git checkout -b feature/YourFeature). +3. Commit your changes (git commit -m 'Add some feature'). +4. Push to the branch (git push origin feature/YourFeature). +5. Open a Pull Request. + +Please ensure that your code adheres to the existing coding style and that all tests pass before submitting your PR. + +## License + +This project is licensed under the +Creative Commons Attribution-NoDerivatives 4.0 International (CC BY-ND 4.0) +License. See the LICENSE file for details. + +## Acknowledgements + +- Thanks to the Spring Boot team and the open-source community for their continuous contributions. +- Special thanks to contributors who have helped improve the project. + +## Author +SafetyPin Team +- Darrel Danadyaksa Poli - 2206081995 +- Fredo Melvern Tanzil - 2206024713 +- Sefriano Edsel Jieftara Djie - 2206818966= +- Alma Putri Nashrida - 2206814671 +- Andi Salsabila Ardian - 2206083571 +- Muhammad Raihan Akbar - 2206827674 + -- GitLab From 292037689feba51ab48be1bb1bb0dc534bda1ad9 Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Fri, 28 Feb 2025 21:16:52 +0700 Subject: [PATCH 59/70] [REFACTOR] Simplify Mockito argument matchers in AuthenticationControllerTest --- .../controller/AuthenticationControllerTest.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java b/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java index f0de3fd..6ecff60 100644 --- a/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java +++ b/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java @@ -22,7 +22,6 @@ import org.springframework.test.web.servlet.MockMvc; import java.time.LocalDate; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @@ -129,7 +128,7 @@ class AuthenticationControllerTest { user.setSocialId(null); user.setId(1L); - Mockito.when(authenticationService.loginUser(eq("email@example.com"), eq("password"))).thenReturn(user); + Mockito.when(authenticationService.loginUser("email@example.com", "password")).thenReturn(user); mockMvc.perform(post("/api/auth/login-email") .param("email", "email@example.com") @@ -151,7 +150,7 @@ class AuthenticationControllerTest { user.setProvider("GOOGLE"); user.setSocialId("social123"); user.setId(2L); - Mockito.when(authenticationService.loginSocial(eq("social@example.com"))).thenReturn(user); + Mockito.when(authenticationService.loginSocial("social@example.com")).thenReturn(user); mockMvc.perform(post("/api/auth/login-social") .param("email", "social@example.com")) @@ -162,7 +161,7 @@ class AuthenticationControllerTest { @Test void testVerifyOTP_Success() throws Exception { - Mockito.when(authenticationService.verifyOTP(eq("email@example.com"), eq("123456"))).thenReturn(true); + Mockito.when(authenticationService.verifyOTP("email@example.com", "123456")).thenReturn(true); mockMvc.perform(post("/api/auth/verify-otp") .param("email", "email@example.com") @@ -173,7 +172,7 @@ class AuthenticationControllerTest { @Test void testVerifyOTP_Failure() throws Exception { - Mockito.when(authenticationService.verifyOTP(eq("email@example.com"), eq("000000"))).thenReturn(false); + Mockito.when(authenticationService.verifyOTP("email@example.com", "000000")).thenReturn(false); mockMvc.perform(post("/api/auth/verify-otp") .param("email", "email@example.com") @@ -198,7 +197,7 @@ class AuthenticationControllerTest { @Test void testPostContent() throws Exception { - Mockito.when(authenticationService.postContent(eq("email@example.com"), eq("Test Content"))) + Mockito.when(authenticationService.postContent("email@example.com", "Test Content")) .thenReturn("Content posted successfully"); mockMvc.perform(post("/api/auth/post") -- GitLab From b95a288569f5560ad68a2c276684e723d7183059 Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Fri, 28 Feb 2025 21:16:58 +0700 Subject: [PATCH 60/70] [REFACTOR] Update age verification to 16 and improve logging messages in AuthenticationService --- .../service/AuthenticationService.java | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java index c5616f6..97c7e5d 100644 --- a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java +++ b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java @@ -30,11 +30,12 @@ public class AuthenticationService { // Registration using email – includes birthdate and OTP generation public User registerUser(RegistrationRequest request) { - if (calculateAge(request.getBirthdate()) < 18) { - throw new IllegalArgumentException("User must be at least 18 years old"); + if (calculateAge(request.getBirthdate()) < 16) { + throw new IllegalArgumentException("User must be at least 16 years old"); } - if (userRepository.findByEmail(request.getEmail()) != null) { - throw new UserAlreadyExistsException("User already exists with this email. If you registered using social login, please sign in with Google/Apple."); + User existingUser = userRepository.findByEmail(request.getEmail()); + if (existingUser != null) { + 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()); @@ -49,14 +50,14 @@ public class AuthenticationService { user.setSocialId(null); user = userRepository.save(user); otpService.generateOTP(request.getEmail()); - logger.info("OTP generated for {} at {}", request.getEmail(), java.time.LocalDateTime.now()); + logger.info("OTP generated for user at {}", java.time.LocalDateTime.now()); return user; } // Social registration/login – simulating data fetched from Google/Apple public User socialLogin(SocialLoginRequest request) { - if (calculateAge(request.getBirthdate()) < 18) { - throw new IllegalArgumentException("User must be at least 18 years old"); + if (calculateAge(request.getBirthdate()) < 16) { + throw new IllegalArgumentException("User must be at least 16 years old"); } User existing = userRepository.findByEmail(request.getEmail()); if (existing != null) { @@ -76,7 +77,7 @@ public class AuthenticationService { user.setSocialId(request.getSocialId()); user = userRepository.save(user); - logger.info("User registered via {}: {} at {}", request.getProvider(), request.getEmail(), java.time.LocalDateTime.now()); + logger.info("User registered via social login at {}", java.time.LocalDateTime.now()); return user; } @@ -85,12 +86,12 @@ public class AuthenticationService { User user = userRepository.findByEmail(email); if (user == null) { // email not exists - logger.warn("Login failed: Email not found for {}", email); + logger.warn("Login failed: Email not found"); throw new InvalidCredentialsException("Invalid email"); } if (!passwordEncoder.matches(rawPassword, user.getPassword())) { // incorrect password - logger.warn("Login failed: Incorrect password for {}", email); + logger.warn("Login failed: Incorrect password attempt"); throw new InvalidCredentialsException("Invalid password"); } logger.info("User logged in: {} at {}", email, java.time.LocalDateTime.now()); @@ -103,7 +104,7 @@ public class AuthenticationService { if (user == null) { throw new InvalidCredentialsException("Social login failed: Email not found"); } - logger.info("User logged in via social: {} at {}", email, java.time.LocalDateTime.now()); + logger.info("User logged in via social authentication at {}", java.time.LocalDateTime.now()); return user; } @@ -115,10 +116,10 @@ public class AuthenticationService { if (user != null) { user.setVerified(true); userRepository.save(user); - logger.info("OTP verified for {} at {}", email, java.time.LocalDateTime.now()); + logger.info("OTP successfully verified at {}", java.time.LocalDateTime.now()); } } else { - logger.warn("OTP verification failed for {} at {}", email, java.time.LocalDateTime.now()); + logger.warn("OTP verification failed at {}", java.time.LocalDateTime.now()); } return result; } @@ -130,7 +131,7 @@ public class AuthenticationService { 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 for {} at {}", email, java.time.LocalDateTime.now()); + logger.info("Password reset requested at {}", java.time.LocalDateTime.now()); } // Example method representing posting content that requires a verified account -- GitLab From e0b3158c002413df6f770958d180222bd0e0fc35 Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Fri, 28 Feb 2025 21:17:05 +0700 Subject: [PATCH 61/70] [REFACTOR] Remove unused import of AuthenticationService in DevDataSeeder --- .../java/com/safetypin/authentication/seeder/DevDataSeeder.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java b/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java index 7b9a5b2..448c2bd 100644 --- a/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java +++ b/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java @@ -4,7 +4,6 @@ import com.safetypin.authentication.model.User; import com.safetypin.authentication.repository.UserRepository; import static com.safetypin.authentication.service.AuthenticationService.EMAIL_PROVIDER; -import com.safetypin.authentication.service.AuthenticationService; import jakarta.annotation.PostConstruct; import org.springframework.context.annotation.Profile; import org.springframework.security.crypto.password.PasswordEncoder; -- GitLab From 2de6ad536a36c4decdf059a3d44aac4271dc04ba Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Fri, 28 Feb 2025 21:29:14 +0700 Subject: [PATCH 62/70] [REFACTOR] Update age verification in AuthenticationServiceTest to 16 years old --- .../service/AuthenticationServiceTest.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java b/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java index 32b2265..97b3d10 100644 --- a/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java +++ b/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java @@ -43,12 +43,12 @@ class AuthenticationServiceTest { request.setPassword("password"); request.setName("Test User"); // set birthdate to 17 years old - request.setBirthdate(LocalDate.now().minusYears(17)); + request.setBirthdate(LocalDate.now().minusYears(15)); Exception exception = assertThrows(IllegalArgumentException.class, () -> authenticationService.registerUser(request) ); - assertEquals("User must be at least 18 years old", exception.getMessage()); + assertEquals("User must be at least 16 years old", exception.getMessage()); } @Test @@ -64,7 +64,7 @@ class AuthenticationServiceTest { Exception exception = assertThrows(UserAlreadyExistsException.class, () -> authenticationService.registerUser(request) ); - assertTrue(exception.getMessage().contains("User already exists with this email")); + assertTrue(exception.getMessage().contains("Email address is already registered")); } @Test @@ -104,7 +104,7 @@ class AuthenticationServiceTest { SocialLoginRequest request = new SocialLoginRequest(); request.setEmail("social@example.com"); request.setName("Social User"); - request.setBirthdate(LocalDate.now().minusYears(17)); + request.setBirthdate(LocalDate.now().minusYears(15)); request.setProvider("GOOGLE"); request.setSocialId("social123"); request.setSocialToken("token"); @@ -112,7 +112,7 @@ class AuthenticationServiceTest { Exception exception = assertThrows(IllegalArgumentException.class, () -> authenticationService.socialLogin(request) ); - assertEquals("User must be at least 18 years old", exception.getMessage()); + assertEquals("User must be at least 16 years old", exception.getMessage()); } @Test -- GitLab From f1e8619074c702c153072c2e6ddf98bf9d8ec5e2 Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Fri, 28 Feb 2025 21:40:41 +0700 Subject: [PATCH 63/70] [REFACTOR] Improve logging messages in AuthenticationService for clarity --- .../authentication/service/AuthenticationService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java index 97c7e5d..ebeb393 100644 --- a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java +++ b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java @@ -94,7 +94,7 @@ public class AuthenticationService { logger.warn("Login failed: Incorrect password attempt"); throw new InvalidCredentialsException("Invalid password"); } - logger.info("User logged in: {} at {}", email, java.time.LocalDateTime.now()); + logger.info("User logged in at {}", java.time.LocalDateTime.now()); return user; } @@ -143,7 +143,7 @@ public class AuthenticationService { if (!user.isVerified()) { return "Your account is not verified. Please complete OTP verification. You may request a new OTP after 2 minutes."; } - logger.info("AuthenticationService.postContent :: Content posted: {}", content); + logger.info("AuthenticationService.postContent :: Content posted by user with email: {}", email); // For demo purposes, we assume the post is successful. return "Content posted successfully"; } -- GitLab From 9f51f8381dc0d767a9a9b82b76cb1da50d769a48 Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Fri, 28 Feb 2025 21:40:52 +0700 Subject: [PATCH 64/70] [REFACTOR] Add NOSONAR comments to password encoding in DevDataSeeder --- .../safetypin/authentication/seeder/DevDataSeeder.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java b/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java index 448c2bd..e864402 100644 --- a/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java +++ b/src/main/java/com/safetypin/authentication/seeder/DevDataSeeder.java @@ -35,7 +35,7 @@ public class DevDataSeeder implements Runnable { User user1 = new User(); user1.setEmail("user1@example.com"); - user1.setPassword(passwordEncoder.encode("password1")); + user1.setPassword(passwordEncoder.encode("password1")); //NOSONAR user1.setName("User One"); user1.setVerified(true); user1.setRole("user"); @@ -46,7 +46,7 @@ public class DevDataSeeder implements Runnable { User user2 = new User(); user2.setEmail("user2@example.com"); - user2.setPassword(passwordEncoder.encode("password2")); + user2.setPassword(passwordEncoder.encode("password2")); //NOSONAR user2.setName("User Two"); user2.setVerified(true); user2.setRole("user"); @@ -58,7 +58,7 @@ public class DevDataSeeder implements Runnable { User user3 = new User(); user3.setEmail("user3@example.com"); - user3.setPassword(passwordEncoder.encode("password3")); + user3.setPassword(passwordEncoder.encode("password3")); //NOSONAR user3.setName("User Three"); user3.setVerified(true); user3.setRole("user"); @@ -69,7 +69,7 @@ public class DevDataSeeder implements Runnable { User user4 = new User(); user4.setEmail("user4@example.com"); - user4.setPassword(passwordEncoder.encode("password4")); + user4.setPassword(passwordEncoder.encode("password4")); //NOSONAR user4.setName("User Four"); user4.setVerified(true); user4.setRole("user"); @@ -80,7 +80,7 @@ public class DevDataSeeder implements Runnable { User user5 = new User(); user5.setEmail("user5@example.com"); - user5.setPassword(passwordEncoder.encode("password5")); + user5.setPassword(passwordEncoder.encode("password5")); //NOSONAR user5.setName("User Five"); user5.setVerified(true); user5.setRole("user"); -- GitLab From a99ae5fd1215590fcc77f517efbed7f1602c8a3e Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Fri, 28 Feb 2025 21:45:48 +0700 Subject: [PATCH 65/70] [REFACTOR] Enhance logging in AuthenticationService to include content details --- .../safetypin/authentication/service/AuthenticationService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java index ebeb393..88df72f 100644 --- a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java +++ b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java @@ -143,7 +143,7 @@ public class AuthenticationService { if (!user.isVerified()) { return "Your account is not verified. Please complete OTP verification. You may request a new OTP after 2 minutes."; } - logger.info("AuthenticationService.postContent :: Content posted by user with email: {}", email); + logger.info("AuthenticationService.postContent :: Content {} posted by user with email: {}", content, email); // For demo purposes, we assume the post is successful. return "Content posted successfully"; } -- GitLab From f1a8916f624bfeb3f95ec6e5b8daadc630c2a2a6 Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Fri, 28 Feb 2025 21:54:15 +0700 Subject: [PATCH 66/70] [REFACTOR] Update content posting test to verify response message format --- .../authentication/service/AuthenticationServiceTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java b/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java index 97b3d10..1dbe0c1 100644 --- a/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java +++ b/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java @@ -430,7 +430,7 @@ class AuthenticationServiceTest { user.setSocialId(null); when(userRepository.findByEmail("test@example.com")).thenReturn(user); - String response = authenticationService.postContent("test@example.com", "Content"); - assertEquals("Content posted successfully", response); + String response = authenticationService.postContent("test@example.com", "ThisIsAContent"); + assertEquals("Content 'ThisIsAContent' posted successfully.", response); } } -- GitLab From 26e794d29413ca69727d2df8f6893aa12170b0da Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Fri, 28 Feb 2025 21:54:19 +0700 Subject: [PATCH 67/70] [REFACTOR] Update logging and response message format in postContent method --- .../authentication/service/AuthenticationService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java index 88df72f..4dd868e 100644 --- a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java +++ b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java @@ -143,9 +143,9 @@ public class AuthenticationService { if (!user.isVerified()) { return "Your account is not verified. Please complete OTP verification. You may request a new OTP after 2 minutes."; } - logger.info("AuthenticationService.postContent :: Content {} posted by user with email: {}", content, email); + logger.info("AuthenticationService.postContent :: Content posted by user with email: {}", email); // For demo purposes, we assume the post is successful. - return "Content posted successfully"; + return String.format("Content '%s' posted successfully.", content); } private int calculateAge(LocalDate birthdate) { -- GitLab From 3db5e91eb8c0c84e4e773d06cda43d5a4006b992 Mon Sep 17 00:00:00 2001 From: KronosDP <darrel.danadyaksa19@gmail.com> Date: Fri, 28 Feb 2025 22:01:32 +0700 Subject: [PATCH 68/70] [REFACTOR] Simplify response messages in postContent method and update corresponding tests --- .../authentication/service/AuthenticationService.java | 6 +++--- .../authentication/service/AuthenticationServiceTest.java | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java index 4dd868e..71e3683 100644 --- a/src/main/java/com/safetypin/authentication/service/AuthenticationService.java +++ b/src/main/java/com/safetypin/authentication/service/AuthenticationService.java @@ -135,7 +135,7 @@ public class AuthenticationService { } // Example method representing posting content that requires a verified account - public String postContent(String email, String content) { + public String postContent(String email, String content) { // NOSONAR User user = userRepository.findByEmail(email); if (user == null) { return "User not found. Please register."; @@ -143,9 +143,9 @@ public class AuthenticationService { if (!user.isVerified()) { return "Your account is not verified. Please complete OTP verification. You may request a new OTP after 2 minutes."; } - logger.info("AuthenticationService.postContent :: Content posted by user with email: {}", email); + logger.info("Content posted successfully by user"); // For demo purposes, we assume the post is successful. - return String.format("Content '%s' posted successfully.", content); + return "Content posted successfully"; } private int calculateAge(LocalDate birthdate) { diff --git a/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java b/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java index 1dbe0c1..97b3d10 100644 --- a/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java +++ b/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java @@ -430,7 +430,7 @@ class AuthenticationServiceTest { user.setSocialId(null); when(userRepository.findByEmail("test@example.com")).thenReturn(user); - String response = authenticationService.postContent("test@example.com", "ThisIsAContent"); - assertEquals("Content 'ThisIsAContent' posted successfully.", response); + String response = authenticationService.postContent("test@example.com", "Content"); + assertEquals("Content posted successfully", response); } } -- GitLab From c22a73be25921c86bc816eab1a29571226da933b Mon Sep 17 00:00:00 2001 From: riorio805 <sefrianojieftara@gmail.com> Date: Fri, 28 Feb 2025 22:12:27 +0700 Subject: [PATCH 69/70] [REFACTOR] remove unneeded dependency --- pom.xml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pom.xml b/pom.xml index bfbd19b..e9bfa77 100644 --- a/pom.xml +++ b/pom.xml @@ -85,12 +85,6 @@ <scope>runtime</scope> </dependency> - <dependency> - <groupId>net.datafaker</groupId> - <artifactId>datafaker</artifactId> - <version>2.4.2</version> - </dependency> - <!-- REST-assured for integration testing --> <dependency> <groupId>io.rest-assured</groupId> -- GitLab From 50bc3759dfc0a06dc28195cb16d5ef6f475c33b2 Mon Sep 17 00:00:00 2001 From: riorio805 <sefrianojieftara@gmail.com> Date: Fri, 28 Feb 2025 22:13:29 +0700 Subject: [PATCH 70/70] [REFACTOR] Change production-cd workflow to use production.yaml file was renamed from resources.yaml --- .github/workflows/production-cd.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/production-cd.yml b/.github/workflows/production-cd.yml index 1e4a411..705daa4 100644 --- a/.github/workflows/production-cd.yml +++ b/.github/workflows/production-cd.yml @@ -49,5 +49,5 @@ jobs: GOOGLE_PROJECT: ${{ secrets.GOOGLE_PROJECT }} run: | gcloud container clusters get-credentials safetypin-cluster --region asia-southeast2 - sed -i "s/GOOGLE_PROJECT/$GOOGLE_PROJECT/g" resources.yaml - kubectl apply -f resources.yaml \ No newline at end of file + sed -i "s/GOOGLE_PROJECT/$GOOGLE_PROJECT/g" production.yaml + kubectl apply -f production.yaml \ No newline at end of file -- GitLab