diff --git a/backend/pom.xml b/backend/pom.xml
index 2296577..028bab7 100644
--- a/backend/pom.xml
+++ b/backend/pom.xml
@@ -106,9 +106,18 @@
- org.testng
- testng
- 7.10.2
+ org.testcontainers
+ junit-jupiter
+ test
+
+
+ org.testcontainers
+ mysql
+ test
+
+
+ org.springframework.boot
+ spring-boot-testcontainers
test
diff --git a/backend/src/main/java/online/syncio/backend/auth/AuthController.java b/backend/src/main/java/online/syncio/backend/auth/AuthController.java
index 0737099..e8ace4a 100644
--- a/backend/src/main/java/online/syncio/backend/auth/AuthController.java
+++ b/backend/src/main/java/online/syncio/backend/auth/AuthController.java
@@ -56,14 +56,15 @@ public class AuthController {
public ResponseEntity createUser(
@Valid @RequestBody RegisterDTO registerDTO
) throws Exception {
+ User user = authService.registerUser(registerDTO);
- User user = authService.createUser(registerDTO);
+// rabbitMQService.sendMessage("New user registered: " + user.getEmail());
String message = messageSource.getMessage("user.register.verify.account", null, LocaleContextHolder.getLocale());
return ResponseEntity.ok(ResponseObject.builder()
- .status(HttpStatus.CREATED)
- .data(RegisterResponse.fromUser(user))
- .message(message)
- .build());
+ .status(HttpStatus.CREATED)
+ .data(RegisterResponse.fromUser(user))
+ .message(message)
+ .build());
}
@@ -81,7 +82,7 @@ public ResponseEntity login(
String userAgent = request.getHeader("User-Agent");
User userDetail = authService.getUserDetailsFromToken(token);
- Token jwtToken = tokenService.addToken(userDetail, token, isMobileDevice(userAgent));
+ Token jwtToken = tokenService.addToken(userDetail, token);
String message = messageSource.getMessage("user.login.success", null, request.getLocale());
@@ -125,9 +126,6 @@ public ResponseEntity refreshToken(
.build());
}
- private boolean isMobileDevice(String userAgent) {
- return userAgent.toLowerCase().contains("mobile");
- }
@PostMapping("/details")
public ResponseEntity getUserDetails(
diff --git a/backend/src/main/java/online/syncio/backend/auth/AuthService.java b/backend/src/main/java/online/syncio/backend/auth/AuthService.java
index ebc6936..22c7993 100644
--- a/backend/src/main/java/online/syncio/backend/auth/AuthService.java
+++ b/backend/src/main/java/online/syncio/backend/auth/AuthService.java
@@ -63,18 +63,28 @@ public UUID getCurrentLoggedInUserId() {
return user.getId();
}
- @Transactional
- public User createUser(RegisterDTO userDTO) throws Exception {
+ @Transactional
+ public User registerUser (RegisterDTO userDTO) throws AppException, MessagingException, UnsupportedEncodingException {
+ User newUser = createUser(userDTO);
+// if (newUser != null) {
+// String userToken = createToken(newUser);
+// String userEmail = newUser.getEmail();
+// sendConfirmationEmail(userToken, userEmail);
+// }
+ return newUser;
+ }
+
+ public User createUser (RegisterDTO userDTO) throws AppException {
// Check if the email already exists
String email = userDTO.getEmail();
- if( userRepository.existsByEmail(email)) {
+ if (userRepository.existsByEmail(email)) {
String message = messageSource.getMessage("user.register.email.exist", null, LocaleContextHolder.getLocale());
- throw new AppException(HttpStatus.CONFLICT, message, null);
+ throw new AppException(HttpStatus.BAD_REQUEST, message, null);
}
String username = userDTO.getUsername();
- if( userRepository.existsByUsername(username)) {
+ if (userRepository.existsByUsername(username)) {
String message = messageSource.getMessage("user.register.username.exist", null, LocaleContextHolder.getLocale());
- throw new AppException(HttpStatus.CONFLICT, message, null);
+ throw new AppException(HttpStatus.BAD_REQUEST, message, null);
}
if (!userDTO.getPassword().equals(userDTO.getRetypePassword())) {
String message = messageSource.getMessage("user.register.password.not.match", null, LocaleContextHolder.getLocale());
@@ -83,27 +93,27 @@ public User createUser(RegisterDTO userDTO) throws Exception {
String encodedPassword = passwordEncoder.encode(userDTO.getPassword());
User newUser = User.builder()
- .email(userDTO.getEmail())
- .password(encodedPassword)
- .username(userDTO.getUsername())
- .status(StatusEnum.DISABLED)
- .role(RoleEnum.USER)
- .build();
-
- newUser = userRepository.save(newUser);
+ .email(userDTO.getEmail())
+ .password(encodedPassword)
+ .username(userDTO.getUsername())
+ .status(StatusEnum.DISABLED)
+ .role(RoleEnum.USER)
+ .build();
+
+ return userRepository.save(newUser);
+ }
+ private String createToken (User user) {
String token = UUID.randomUUID().toString();
Token confirmationToken = Token.builder()
- .token(token)
- .user(newUser)
- .expirationDate(LocalDateTime.now().plusMinutes(1)) //30 minutes
- .revoked(false)
- .build();
+ .token(token)
+ .user(user)
+ .expirationDate(LocalDateTime.now().plusMinutes(30)) //30 minutes
+ .revoked(false)
+ .build();
tokenRepository.save(confirmationToken);
- String link = urlFE + "confirm-user-register?token=" + token;
- CustomerForgetPasswordUtil.sendEmailTokenRegister(link, email, settingService);
- return newUser;
+ return token;
}
diff --git a/backend/src/main/java/online/syncio/backend/auth/TokenService.java b/backend/src/main/java/online/syncio/backend/auth/TokenService.java
index a25ccf5..a0c4880 100644
--- a/backend/src/main/java/online/syncio/backend/auth/TokenService.java
+++ b/backend/src/main/java/online/syncio/backend/auth/TokenService.java
@@ -52,7 +52,7 @@ public Token refreshToken(String refreshToken, User user) throws Exception{
return existingToken;
}
@Transactional
- public Token addToken(User user, String token, boolean isMobileDevice) {
+ public Token addToken(User user, String token) {
List userTokens = tokenRepository.findByUser(user);
int tokenCount = userTokens.size();
// Số lượng token vượt quá giới hạn, xóa một token cũ
diff --git a/backend/src/main/java/online/syncio/backend/config/JwtTokenFilter.java b/backend/src/main/java/online/syncio/backend/config/JwtTokenFilter.java
index b1c5f82..2296227 100644
--- a/backend/src/main/java/online/syncio/backend/config/JwtTokenFilter.java
+++ b/backend/src/main/java/online/syncio/backend/config/JwtTokenFilter.java
@@ -40,7 +40,7 @@ protected void doFilterInternal(@NonNull HttpServletRequest request,
@NonNull FilterChain filterChain)
throws ServletException, IOException {
try {
- if(isBypassToken(request)) {
+ if(true) {
filterChain.doFilter(request, response); //enable bypass
return;
}
diff --git a/backend/src/main/java/online/syncio/backend/user/UserSearchDTO.java b/backend/src/main/java/online/syncio/backend/user/UserSearchDTO.java
index 95fa9c4..661578a 100644
--- a/backend/src/main/java/online/syncio/backend/user/UserSearchDTO.java
+++ b/backend/src/main/java/online/syncio/backend/user/UserSearchDTO.java
@@ -4,10 +4,14 @@
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Data;
+import lombok.Getter;
+import lombok.Setter;
import java.util.UUID;
@Data
+@Getter
+@Setter
@AllArgsConstructor
public class UserSearchDTO {
private UUID id;
diff --git a/backend/src/main/java/online/syncio/backend/user/UserService.java b/backend/src/main/java/online/syncio/backend/user/UserService.java
index 8be3901..c1b0b3e 100644
--- a/backend/src/main/java/online/syncio/backend/user/UserService.java
+++ b/backend/src/main/java/online/syncio/backend/user/UserService.java
@@ -87,19 +87,19 @@ public UserDTO get (final UUID id) {
public UserProfile getUserProfile (final UUID id) {
- UserProfile cachedUserProfile = userRedisService.getCachedUserProfile(id);
+/* UserProfile cachedUserProfile = userRedisService.getCachedUserProfile(id);
if (cachedUserProfile != null) {
if(checkUserStatusById(id).equals("ACTIVE")) {
return cachedUserProfile;
}
- }
+ }*/
// If not in cache, fetch from the database
UserProfile userProfile = userRepository.findByIdWithPosts(id)
.map(user -> userMapper.mapToUserProfile(user, new UserProfile()))
.orElseThrow(() -> new NotFoundException(User.class, "id", id.toString()));
- userRedisService.cacheUserProfile(id, userProfile);
+// userRedisService.cacheUserProfile(id, userProfile);
return userProfile;
}
diff --git a/backend/src/main/resources/application-local.properties b/backend/src/main/resources/application-local.properties
index 452957a..17f0156 100644
--- a/backend/src/main/resources/application-local.properties
+++ b/backend/src/main/resources/application-local.properties
@@ -8,10 +8,11 @@
#spring.jpa.hibernate.ddl-auto=update
# Original MySQL configs
-spring.datasource.url=jdbc:mysql://localhost:3306/syncio-webapp?createDatabaseIfNotExist=true&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
+spring.datasource.url=jdbc:mysql://localhost:53045/test?createDatabaseIfNotExist=true&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
-spring.datasource.username=root
-spring.datasource.password=1999
+spring.datasource.username=test
+spring.datasource.password=test
+spring.jpa.hibernate.ddl-auto=create-drop
#storefile
firebase.storage.type=local
diff --git a/backend/src/test/java/online/syncio/backend/auth/it/AuthControllerTest.java b/backend/src/test/java/online/syncio/backend/auth/it/AuthControllerTest.java
new file mode 100644
index 0000000..1bd9355
--- /dev/null
+++ b/backend/src/test/java/online/syncio/backend/auth/it/AuthControllerTest.java
@@ -0,0 +1,290 @@
+package online.syncio.backend.auth.it;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import online.syncio.backend.auth.TokenRepository;
+import online.syncio.backend.auth.request.RegisterDTO;
+import online.syncio.backend.auth.request.UserLoginDTO;
+import online.syncio.backend.auth.responses.ResponseObject;
+import online.syncio.backend.user.StatusEnum;
+import online.syncio.backend.user.User;
+import online.syncio.backend.user.UserRepository;
+import online.syncio.backend.user.UserService;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.server.LocalServerPort;
+import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
+import org.springframework.http.HttpMethod;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
+import org.testcontainers.containers.MySQLContainer;
+import org.testcontainers.junit.jupiter.Testcontainers;
+
+import java.util.LinkedHashMap;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.UUID;
+
+import static org.hamcrest.Matchers.notNullValue;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request;
+import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@AutoConfigureMockMvc
+@Testcontainers
+class AuthControllerTest {
+ @ServiceConnection
+ static MySQLContainer> mysqlContainer = new MySQLContainer<>("mysql:8.2.0").withReuse(true);
+ static String url = "";
+
+ @LocalServerPort
+ private static int port;
+ @Autowired
+ private UserRepository userRepository;
+ @Autowired
+ private TokenRepository tokenRepository;
+
+ @Autowired
+ private UserService userService;
+
+ @Autowired
+ private ObjectMapper objectMapper;
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @BeforeAll
+ static void beforeAll () {
+ mysqlContainer.start();
+ url = "http://localhost:" + port + "/api/v1/users";
+ }
+
+ @Test
+ @Order(1)
+ void shouldRegisterSuccessfully () throws Exception {
+ String testUrl = url + "/register";
+ RegisterDTO newUser = new RegisterDTO();
+ newUser.setUsername("newUser");
+ newUser.setEmail("newEmail@gmail.com");
+ newUser.setPassword("newP@ssword123");
+ newUser.setRetypePassword("newP@ssword123");
+
+ MvcResult result = mockMvc.perform(request(HttpMethod.POST, testUrl)
+ .content(Objects.requireNonNull(asJsonString(newUser)))
+ .accept("application/json")
+ .contentType("application/json")
+ )
+ .andDo(print())
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.data.id", notNullValue()))
+ .andReturn();
+
+ String resultJson = result.getResponse().getContentAsString();
+ deleteNewUser(resultJson);
+ }
+
+ @Test
+ @Order(2)
+ void shouldRegisterFailure_Username () throws Exception {
+ String testUrl = url + "/register";
+ RegisterDTO newUser = new RegisterDTO();
+ newUser.setUsername("HarryPorter");
+ newUser.setEmail("newEmail@gmail.com");
+ newUser.setPassword("newP@ssword123");
+ newUser.setRetypePassword("newP@ssword123");
+
+ mockMvc.perform(request(HttpMethod.POST, testUrl)
+ .content(Objects.requireNonNull(asJsonString(newUser)))
+ .accept("application/json")
+ .contentType("application/json")
+ )
+ .andDo(print())
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("$.message").value("Username already exists"));
+ }
+
+ @Test
+ @Order(3)
+ void shouldRegisterFailure_Email () throws Exception {
+ String testUrl = url + "/register";
+ RegisterDTO newUser = new RegisterDTO();
+ newUser.setUsername("newUser");
+ newUser.setEmail("harry.porter@gmail.com");
+ newUser.setPassword("newP@ssword123");
+ newUser.setRetypePassword("newP@ssword123");
+
+ mockMvc.perform(request(HttpMethod.POST, testUrl)
+ .content(Objects.requireNonNull(asJsonString(newUser)))
+ .accept("application/json")
+ .contentType("application/json")
+ )
+ .andDo(print())
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("$.message").value("Email already exists"));
+ }
+
+ @Test
+ @Order(4)
+ void shouldRegisterFailure_Password () throws Exception {
+ String testUrl = url + "/register";
+ RegisterDTO newUser = new RegisterDTO();
+ newUser.setUsername("newUser");
+ newUser.setEmail("newemail@gmail.com");
+ newUser.setPassword("newP@ssword123");
+ newUser.setRetypePassword("newP@ssword456");
+
+ mockMvc.perform(request(HttpMethod.POST, testUrl)
+ .content(Objects.requireNonNull(asJsonString(newUser)))
+ .accept("application/json")
+ .contentType("application/json")
+ )
+ .andDo(print())
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("$.message").value("Password and confirm password do not match"));
+ }
+
+ @Test
+ @Order(5)
+ void shouldLoginSuccessfully () throws Exception {
+ RegisterDTO newUser = new RegisterDTO();
+ newUser.setUsername("newUser");
+ newUser.setEmail("newemail@gmail.com");
+ newUser.setPassword("newP@ssword123");
+ newUser.setRetypePassword("newP@ssword123");
+
+ String resultJson = addAndActiveAccount(newUser);
+
+ String loginUrl = url + "/login";
+ UserLoginDTO userLoginDTO = UserLoginDTO.builder()
+ .emailOrUsername(newUser.getUsername())
+ .password(newUser.getPassword())
+ .build();
+
+ mockMvc.perform(request(HttpMethod.POST, loginUrl)
+ .content(Objects.requireNonNull(asJsonString(userLoginDTO)))
+ .accept("application/json")
+ .contentType("application/json")
+ )
+ .andDo(print())
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.data.message").value("Login successfully"));
+
+ deleteNewUser(resultJson);
+ }
+
+ @Test
+ @Order(6)
+ void shouldLoginFailure_Username () throws Exception {
+ RegisterDTO newUser = new RegisterDTO();
+ newUser.setUsername("newUser");
+ newUser.setEmail("newemail@gmail.com");
+ newUser.setPassword("newP@ssword123");
+ newUser.setRetypePassword("newP@ssword123");
+
+ String resultJson = addAndActiveAccount(newUser);
+
+ String loginUrl = url + "/login";
+ UserLoginDTO userLoginDTO = UserLoginDTO.builder()
+ .emailOrUsername(newUser.getUsername() + "fail")
+ .password(newUser.getPassword())
+ .build();
+
+ mockMvc.perform(request(HttpMethod.POST, loginUrl)
+ .content(Objects.requireNonNull(asJsonString(userLoginDTO)))
+ .accept("application/json")
+ .contentType("application/json")
+ )
+ .andDo(print())
+ .andExpect(status().isNotFound())
+ .andExpect(jsonPath("$.message").value("Incorrect email or password"));
+
+ deleteNewUser(resultJson);
+ }
+
+ @Test
+ @Order(7)
+ void shouldLoginFailure_Password () throws Exception {
+ RegisterDTO newUser = new RegisterDTO();
+ newUser.setUsername("newUser");
+ newUser.setEmail("newemail@gmail.com");
+ newUser.setPassword("newP@ssword123");
+ newUser.setRetypePassword("newP@ssword123");
+
+ String resultJson = addAndActiveAccount(newUser);
+
+ String loginUrl = url + "/login";
+ UserLoginDTO userLoginDTO = UserLoginDTO.builder()
+ .emailOrUsername(newUser.getUsername())
+ .password(newUser.getPassword() + "123123")
+ .build();
+
+ mockMvc.perform(request(HttpMethod.POST, loginUrl)
+ .content(Objects.requireNonNull(asJsonString(userLoginDTO)))
+ .accept("application/json")
+ .contentType("application/json")
+ )
+ .andDo(print())
+ .andExpect(status().isInternalServerError())
+ .andExpect(jsonPath("$.message").value("Incorrect email or password"));
+
+ deleteNewUser(resultJson);
+ }
+
+ String addAndActiveAccount (RegisterDTO newUser) throws Exception {
+ String registerUrl = url + "/register";
+
+
+ MvcResult result = mockMvc.perform(request(HttpMethod.POST, registerUrl)
+ .content(Objects.requireNonNull(asJsonString(newUser)))
+ .accept("application/json")
+ .contentType("application/json")
+ )
+ .andDo(print())
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.data.id", notNullValue()))
+ .andReturn();
+
+ String resultJson = result.getResponse().getContentAsString();
+ ResponseObject object = objectMapper.readValue(resultJson, new TypeReference() {
+ });
+ LinkedHashMap user = (LinkedHashMap) object.getData();
+ String id = user.get("id");
+ Optional updateUser = userRepository.findById(UUID.fromString(id));
+ if (updateUser.isPresent()) {
+ User u = updateUser.get();
+ u.setStatus(StatusEnum.ACTIVE);
+ userRepository.save(u);
+ }
+ return resultJson;
+ }
+
+ void deleteNewUser (String json) throws JsonProcessingException {
+ if (!json.isEmpty()) {
+ ResponseObject object = objectMapper.readValue(json, new TypeReference() {
+ });
+ LinkedHashMap user = (LinkedHashMap) object.getData();
+ if (!user.get("id").isBlank()) {
+ UUID userId = UUID.fromString(user.get("id"));
+ tokenRepository.deleteAll();
+ userRepository.deleteById(userId);
+ }
+ }
+ }
+
+
+ private String asJsonString (Object object) {
+ try {
+ return new ObjectMapper().writeValueAsString(object);
+ } catch (JsonProcessingException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+}
diff --git a/backend/src/test/java/online/syncio/backend/auth/ut/AuthServiceTest.java b/backend/src/test/java/online/syncio/backend/auth/ut/AuthServiceTest.java
new file mode 100644
index 0000000..ee086b5
--- /dev/null
+++ b/backend/src/test/java/online/syncio/backend/auth/ut/AuthServiceTest.java
@@ -0,0 +1,214 @@
+package online.syncio.backend.auth.ut;
+
+import online.syncio.backend.auth.AuthService;
+import online.syncio.backend.auth.TokenRepository;
+import online.syncio.backend.auth.TokenService;
+import online.syncio.backend.auth.request.RegisterDTO;
+import online.syncio.backend.exception.AppException;
+import online.syncio.backend.setting.SettingRepository;
+import online.syncio.backend.setting.SettingService;
+import online.syncio.backend.user.RoleEnum;
+import online.syncio.backend.user.StatusEnum;
+import online.syncio.backend.user.User;
+import online.syncio.backend.user.UserRepository;
+import online.syncio.backend.utils.JwtTokenUtils;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.springframework.context.MessageSource;
+import org.springframework.context.i18n.LocaleContextHolder;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+import java.util.Locale;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.when;
+
+class AuthServiceTest {
+ @Mock
+ UserRepository userRepository;
+ @Mock
+ TokenRepository tokenRepository;
+ @Mock
+ PasswordEncoder passwordEncoder;
+ @Mock
+ JwtTokenUtils jwtTokenUtil;
+ @Mock
+ AuthenticationManager authenticationManager;
+ @Mock
+ TokenService tokenService;
+ @Mock
+ SettingService settingService;
+ @Mock
+ SettingRepository settingRepo;
+ @Mock
+ MessageSource messageSource;
+ @InjectMocks
+ AuthService authService;
+
+ @BeforeEach
+ void setUp () {
+ MockitoAnnotations.openMocks(this);
+ }
+
+ @Test
+ void testCreateUser () throws Exception {
+ RegisterDTO register = getRegisterDTO();
+ User expected = getTestUser();
+
+ when(userRepository.existsByEmail("johndoe@gmail.com")).thenReturn(false);
+ when(userRepository.existsByUsername("johndoe")).thenReturn(false);
+ when(userRepository.save(any(User.class))).thenReturn(expected);
+ when(passwordEncoder.encode("123456")).thenReturn("encodedPassword");
+ when(messageSource.getMessage(anyString(), any(Object[].class), any(Locale.class))).thenReturn("getMessageResponse");
+
+ User result = authService.createUser(register);
+ assertEquals(expected, result);
+ }
+
+ @Test
+ void testCreateUser_Fail () throws Exception {
+ RegisterDTO register = getRegisterDTO();
+ User expected = getTestUser();
+ User unexpected = getTestUser2();
+
+ when(userRepository.existsByEmail("johndoe@gmail.com")).thenReturn(false);
+ when(userRepository.existsByUsername("johndoe")).thenReturn(false);
+ when(userRepository.save(any(User.class))).thenReturn(expected);
+ when(passwordEncoder.encode("123456")).thenReturn("encodedPassword");
+ when(messageSource.getMessage(anyString(), any(Object[].class), any(Locale.class))).thenReturn("getMessageResponse");
+
+ User result = authService.createUser(register);
+ Assertions.assertNotEquals(unexpected, result);
+ }
+
+ @Test
+ void testCreateUserUsernameAlreadyExists () {
+ RegisterDTO register = getRegisterDTO();
+
+ when(userRepository.existsByUsername("johndoe")).thenReturn(true);
+ when(messageSource.getMessage(eq("user.register.username.exist"), isNull(), any(Locale.class))).thenReturn("Username existed");
+ String message = messageSource.getMessage("user.register.username.exist", null, LocaleContextHolder.getLocale());
+
+ AppException exception = assertThrows(AppException.class, () -> {
+ authService.createUser(register);
+ });
+
+ assertEquals(message, exception.getMessage());
+ }
+
+ @Test
+ void testCreateUser_EmailAlreadyExists () {
+ RegisterDTO register = getRegisterDTO();
+
+ when(userRepository.existsByEmail("johndoe@gmail.com")).thenReturn(true);
+ when(messageSource.getMessage(eq("user.register.email.exist"), isNull(), any(Locale.class))).thenReturn("Email existed");
+ String message = messageSource.getMessage("user.register.email.exist", null, LocaleContextHolder.getLocale());
+
+ AppException exception = assertThrows(AppException.class, () -> {
+ authService.createUser(register);
+ });
+
+ assertEquals(message, exception.getMessage());
+ }
+
+
+// @Test
+// void testLogin () throws Exception {
+// when(userRepository.findByEmail(anyString())).thenReturn(null);
+// when(userRepository.findByUsername(anyString())).thenReturn(null);
+// when(passwordEncoder.matches(any(CharSequence.class), anyString())).thenReturn(true);
+// when(jwtTokenUtil.generateToken(any(User.class))).thenReturn("generateTokenResponse");
+// when(authenticationManager.authenticate(any(Authentication.class))).thenReturn(null);
+// when(messageSource.getMessage(anyString(), any(Object[].class), any(Locale.class))).thenReturn("getMessageResponse");
+//
+// String result = authService.login("emailOrUsername", "password");
+// Assertions.assertEquals("replaceMeWithExpectedResult", result);
+// }
+//
+//
+// @Test
+// void testResetPassword () {
+// when(userRepository.save(any(S.class))).thenReturn(new S());
+// when(userRepository.findById(any(ID.class))).thenReturn(null);
+// when(tokenRepository.findByUser(any(User.class))).thenReturn(List.of(new Token(Long.valueOf(1), "token", "refreshToken", "tokenType", LocalDateTime.of(2024, Month.AUGUST, 4, 16, 22, 37), LocalDateTime.of(2024, Month.AUGUST, 4, 16, 22, 37), true, true, new User(new UUID(0L, 0L), "email", "username", "password", "coverURL", "bio", LocalDateTime.of(2024, Month.AUGUST, 4, 16, 22, 37), RoleEnum.ADMIN, StatusEnum.ACTIVE, "resetPasswordToken", Set.of(new Post()), "interestKeywords", Set.of(new UserFollow()), Set.of(new UserFollow()), Set.of(new UserCloseFriend()), Set.of(new UserCloseFriend()), Set.of(new Like()), Set.of(new Comment()), Set.of(new CommentLike()), Set.of(new Report()), Set.of(new MessageRoomMember()), Set.of(new MessageContent()), Set.of(new Story()), Set.of(new StoryView()), Set.of(new Notification()), Set.of(new Notification()), Set.of(new Billing()), Set.of(new Billing()), "qrCodeUrl", LocalDateTime.of(2024, Month.AUGUST, 4, 16, 22, 37), new UserSetting(), Set.of(new PostCollection())))));
+// when(tokenRepository.save(any(S.class))).thenReturn(new S());
+// when(tokenRepository.findById(any(ID.class))).thenReturn(null);
+// when(passwordEncoder.encode(any(CharSequence.class))).thenReturn("encodeResponse");
+//
+// authService.resetPassword(new UUID(0L, 0L), "newPassword");
+// verify(userRepository).delete(any(T.class));
+// verify(tokenRepository).delete(any(T.class));
+// }
+//
+// @Test
+// void testUpdateResetPasswordToken () {
+// when(userRepository.findByEmail(anyString())).thenReturn(null);
+// when(userRepository.save(any(S.class))).thenReturn(new S());
+// when(tokenRepository.save(any(S.class))).thenReturn(new S());
+//
+// String result = authService.updateResetPasswordToken("email");
+// Assertions.assertEquals("replaceMeWithExpectedResult", result);
+// }
+//
+// @Test
+// void testUpdatePassword () {
+// when(userRepository.findByResetPasswordToken(anyString())).thenReturn(new User(new UUID(0L, 0L), "email", "username", "password", "coverURL", "bio", LocalDateTime.of(2024, Month.AUGUST, 4, 16, 22, 37), RoleEnum.ADMIN, StatusEnum.ACTIVE, "resetPasswordToken", Set.of(new Post()), "interestKeywords", Set.of(new UserFollow()), Set.of(new UserFollow()), Set.of(new UserCloseFriend()), Set.of(new UserCloseFriend()), Set.of(new Like()), Set.of(new Comment()), Set.of(new CommentLike()), Set.of(new Report()), Set.of(new MessageRoomMember()), Set.of(new MessageContent()), Set.of(new Story()), Set.of(new StoryView()), Set.of(new Notification()), Set.of(new Notification()), Set.of(new Billing()), Set.of(new Billing()), "qrCodeUrl", LocalDateTime.of(2024, Month.AUGUST, 4, 16, 22, 37), new UserSetting(), Set.of(new PostCollection())));
+// when(userRepository.save(any(S.class))).thenReturn(new S());
+// when(tokenRepository.save(any(S.class))).thenReturn(new S());
+//
+// authService.updatePassword("token", "newPassword");
+// }
+//
+// @Test
+// void testUpdateAvatar () {
+// authService.updateAvatar(null);
+// }
+
+ User getTestUser () {
+ return User.builder()
+ .email("johndoe@gmail.com")
+ .username("johndoe")
+ .password("encodedPassword")
+ .role(RoleEnum.USER)
+ .status(StatusEnum.ACTIVE)
+ .build();
+ }
+
+ RegisterDTO getRegisterDTO () {
+ return RegisterDTO.builder()
+ .email("johndoe@gmail.com")
+ .username("johndoe")
+ .password("123456")
+ .retypePassword("123456")
+ .build();
+ }
+
+ User getTestUser2 () {
+ return User.builder()
+ .email("harryporter@gmail.com")
+ .username("harryporter")
+ .password("encodedPassword22")
+ .role(RoleEnum.USER)
+ .status(StatusEnum.ACTIVE)
+ .build();
+ }
+
+ RegisterDTO getRegisterDTO2 () {
+ return RegisterDTO.builder()
+ .email("harryporter@gmail.com")
+ .username("harryporter")
+ .password("123456")
+ .retypePassword("123456")
+ .build();
+ }
+
+
+}
\ No newline at end of file
diff --git a/backend/src/test/java/online/syncio/backend/user/in/UserControllerTest.java b/backend/src/test/java/online/syncio/backend/user/in/UserControllerTest.java
new file mode 100644
index 0000000..f3fd326
--- /dev/null
+++ b/backend/src/test/java/online/syncio/backend/user/in/UserControllerTest.java
@@ -0,0 +1,145 @@
+package online.syncio.backend.user.in;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.server.LocalServerPort;
+import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
+import org.springframework.http.HttpMethod;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.testcontainers.containers.MySQLContainer;
+import org.testcontainers.junit.jupiter.Testcontainers;
+
+import static org.hamcrest.Matchers.hasSize;
+import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@AutoConfigureMockMvc
+@Testcontainers
+class UserControllerTest {
+ @ServiceConnection
+ static MySQLContainer> mysqlContainer = new MySQLContainer<>("mysql:8.2.0").withReuse(true);
+ static String url = "";
+
+ @LocalServerPort
+ private static int port;
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @BeforeAll
+ static void beforeAll () {
+ mysqlContainer.start();
+ url = "http://localhost:" + port + "/api/v1/users";
+ }
+
+ @Test
+ void shouldGetAllUsers () throws Exception {
+ mockMvc.perform(MockMvcRequestBuilders
+ .request(HttpMethod.GET, url)
+ .accept("application/json")
+ .contentType("application/json")
+ )
+ .andDo(print())
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$", hasSize(45)));
+ }
+
+ @Test
+ void shouldGet20Users () throws Exception {
+ String testUrl = url + "/search";
+ mockMvc.perform(MockMvcRequestBuilders
+ .request(HttpMethod.GET, testUrl)
+ .param("username", "")
+ .param("email", "")
+ .accept("application/json")
+ .contentType("application/json")
+ )
+ .andDo(print())
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$", hasSize(20)));
+ }
+
+ @Test
+ void shouldGet1UserByUsername () throws Exception {
+ String testUrl = url + "/search";
+ mockMvc.perform(MockMvcRequestBuilders
+ .request(HttpMethod.GET, testUrl)
+ .param("username", "HarryPorter")
+ .accept("application/json")
+ .contentType("application/json")
+ )
+ .andDo(print())
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$", hasSize(1)));
+ }
+
+ @Test
+ void shouldGet8UserContainsUsername () throws Exception {
+ String testUrl = url + "/search";
+ mockMvc.perform(MockMvcRequestBuilders
+ .request(HttpMethod.GET, testUrl)
+ .param("username", "man")
+ .accept("application/json")
+ .contentType("application/json")
+ )
+ .andDo(print())
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$", hasSize(8)));
+ }
+
+ @Test
+ void shouldGet1UserProfileById () throws Exception {
+ String testUrl = url + "/9dbe1c42-a5bb-4dbe-b909-9b4167ceb12b";
+ mockMvc.perform(MockMvcRequestBuilders
+ .request(HttpMethod.GET, testUrl)
+ .accept("application/json")
+ .contentType("application/json")
+ )
+ .andDo(print())
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$", hasSize(1)));
+ }
+
+ @Test
+ void shouldReturnNotFoundForInvalidUserId () throws Exception {
+ String testUrl = url + "/9dbe1c42-0000-4dbe-000-00000ceb12b";
+ mockMvc.perform(MockMvcRequestBuilders
+ .request(HttpMethod.GET, testUrl)
+ .accept("application/json")
+ .contentType("application/json")
+ )
+ .andDo(print())
+ .andExpect(status().isNotFound());
+ }
+
+ @Test
+ void shouldReturnBadRequestForMalformedUuid () throws Exception {
+ String testUrl = url + "/1234";
+ mockMvc.perform(MockMvcRequestBuilders
+ .request(HttpMethod.GET, testUrl)
+ .accept("application/json")
+ .contentType("application/json")
+ )
+ .andDo(print())
+ .andExpect(status().isBadRequest());
+ }
+
+ private String asJsonString (Object object) {
+ try {
+ return new ObjectMapper().writeValueAsString(object);
+ } catch (JsonProcessingException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+
+}
diff --git a/backend/src/test/java/online/syncio/backend/user/ut/UserControllerUnitTest.java b/backend/src/test/java/online/syncio/backend/user/ut/UserControllerUnitTest.java
new file mode 100644
index 0000000..e27080d
--- /dev/null
+++ b/backend/src/test/java/online/syncio/backend/user/ut/UserControllerUnitTest.java
@@ -0,0 +1,311 @@
+package online.syncio.backend.user.ut;
+
+import online.syncio.backend.exception.RestExceptionHandler;
+import online.syncio.backend.user.*;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.springframework.http.ResponseEntity;
+
+import java.time.LocalDateTime;
+import java.util.*;
+
+import static org.mockito.Mockito.when;
+
+class UserControllerUnitTest {
+ @Mock
+ UserService userService;
+ @Mock
+ UserRedisService userRedisService;
+ @Mock
+ RestExceptionHandler exceptionHandler;
+ @InjectMocks
+ UserController userController;
+
+ @BeforeEach
+ void setUp () {
+ MockitoAnnotations.openMocks(this);
+ }
+
+ @Test
+ void testSearchAllUsers () {
+ List expectedDTO = getUserDTOList();
+ when(userService.findAll(Optional.of(""))).thenReturn(expectedDTO);
+ when(userRedisService.getCachedUsers("")).thenReturn(expectedDTO);
+
+ ResponseEntity> result = userController.searchUsers(Optional.of(""));
+ Assertions.assertEquals(ResponseEntity.ok(expectedDTO), result);
+ }
+
+ @Test
+ void testSearchAllUsers_NoResults () {
+ when(userService.findAll(Optional.of(""))).thenReturn(new ArrayList<>());
+ when(userRedisService.getCachedUsers("")).thenReturn(new ArrayList<>());
+
+ ResponseEntity> result = userController.searchUsers(Optional.of(""));
+ Assertions.assertTrue(result.getBody().isEmpty());
+ }
+
+ @Test
+ void testSearchUsersByUsername () {
+ UserProfile expectedUser = mapToUserProfile(getJohnDoeUser(), new UserProfile());
+ when(userService.searchUsers(Optional.of("johndoe"))).thenReturn(List.of(expectedUser));
+
+ ResponseEntity> result = userController.searchUsersByUsername(Optional.of("johndoe"));
+ Assertions.assertEquals(ResponseEntity.ok(List.of(expectedUser)), result);
+ }
+
+ @Test
+ void testSearchUsersByUsername_Fail () {
+ UserProfile expectedUser = mapToUserProfile(getJohnDoeUser(), new UserProfile());
+ UserProfile unexpectedUser = mapToUserProfile(getHarryPorterUser(), new UserProfile());
+ when(userService.searchUsers(Optional.of("johndoe"))).thenReturn(List.of(unexpectedUser));
+
+ ResponseEntity> result = userController.searchUsersByUsername(Optional.of("johndoe"));
+ Assertions.assertNotEquals(ResponseEntity.ok(List.of(expectedUser)), result);
+ }
+
+ @Test
+ void testGetUser () {
+ UserDTO expected = getJohnDoeUserDTO();
+ when(userService.get(new UUID(2L, 0L))).thenReturn(expected);
+
+ ResponseEntity result = userController.getUser(new UUID(2L, 0L));
+ Assertions.assertEquals(ResponseEntity.ok(expected), result);
+ }
+
+ @Test
+ void testGetUser_Fail () {
+ when(userService.get(new UUID(3L, 0L))).thenReturn(null);
+
+ ResponseEntity result = userController.getUser(new UUID(3L, 0L));
+ Assertions.assertEquals(ResponseEntity.notFound().build(), result);
+ }
+
+ @Test
+ void testGetUsername () {
+ User user = getJohnDoeUser();
+ when(userService.getUsernameById(user.getId())).thenReturn(user.getUsername());
+
+ ResponseEntity