diff --git a/api/pom.xml b/api/pom.xml
index c16846b8..b5801582 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -43,6 +43,11 @@
postgresql
runtime
+
+ org.springframework.boot
+ spring-boot-starter-validation
+ 3.2.2
+
org.projectlombok
lombok
diff --git a/api/src/main/java/lab/en2b/quizapi/auth/AuthService.java b/api/src/main/java/lab/en2b/quizapi/auth/AuthService.java
index 888d946d..704149b9 100644
--- a/api/src/main/java/lab/en2b/quizapi/auth/AuthService.java
+++ b/api/src/main/java/lab/en2b/quizapi/auth/AuthService.java
@@ -21,9 +21,9 @@
@Service
@RequiredArgsConstructor
public class AuthService {
- private final JwtUtils jwtUtils;
private final AuthenticationManager authenticationManager;
private final UserService userService;
+ private final JwtUtils jwtUtils;
/**
* Creates a session for a user. Throws an 401 unauthorized exception otherwise
* @param loginRequest the request containing the login info
diff --git a/api/src/main/java/lab/en2b/quizapi/auth/dtos/JwtResponseDto.java b/api/src/main/java/lab/en2b/quizapi/auth/dtos/JwtResponseDto.java
index d50c5f83..6e3c4792 100644
--- a/api/src/main/java/lab/en2b/quizapi/auth/dtos/JwtResponseDto.java
+++ b/api/src/main/java/lab/en2b/quizapi/auth/dtos/JwtResponseDto.java
@@ -1,15 +1,15 @@
package lab.en2b.quizapi.auth.dtos;
import com.fasterxml.jackson.annotation.JsonProperty;
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
+import lombok.*;
import java.util.List;
@AllArgsConstructor
@NoArgsConstructor
@Getter
+@Builder
+@EqualsAndHashCode
public class JwtResponseDto {
private String token;
@JsonProperty("refresh_token")
diff --git a/api/src/main/java/lab/en2b/quizapi/auth/dtos/LoginDto.java b/api/src/main/java/lab/en2b/quizapi/auth/dtos/LoginDto.java
index 5e7a0ec5..15a639e9 100644
--- a/api/src/main/java/lab/en2b/quizapi/auth/dtos/LoginDto.java
+++ b/api/src/main/java/lab/en2b/quizapi/auth/dtos/LoginDto.java
@@ -1,5 +1,7 @@
package lab.en2b.quizapi.auth.dtos;
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@@ -10,7 +12,10 @@
@Data
public class LoginDto {
@NonNull
+ @NotBlank
+ @Email
private String email;
@NonNull
+ @NotBlank
private String password;
}
diff --git a/api/src/main/java/lab/en2b/quizapi/auth/dtos/RefreshTokenDto.java b/api/src/main/java/lab/en2b/quizapi/auth/dtos/RefreshTokenDto.java
index f327ae8a..da978fb4 100644
--- a/api/src/main/java/lab/en2b/quizapi/auth/dtos/RefreshTokenDto.java
+++ b/api/src/main/java/lab/en2b/quizapi/auth/dtos/RefreshTokenDto.java
@@ -1,6 +1,7 @@
package lab.en2b.quizapi.auth.dtos;
import com.fasterxml.jackson.annotation.JsonProperty;
+import jakarta.validation.constraints.NotEmpty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@@ -10,7 +11,8 @@
@NoArgsConstructor
@Data
public class RefreshTokenDto {
- @NonNull
@JsonProperty("refresh_token")
+ @NonNull
+ @NotEmpty
private String refreshToken;
}
diff --git a/api/src/main/java/lab/en2b/quizapi/auth/dtos/RefreshTokenResponseDto.java b/api/src/main/java/lab/en2b/quizapi/auth/dtos/RefreshTokenResponseDto.java
index e87000b7..1035ad5d 100644
--- a/api/src/main/java/lab/en2b/quizapi/auth/dtos/RefreshTokenResponseDto.java
+++ b/api/src/main/java/lab/en2b/quizapi/auth/dtos/RefreshTokenResponseDto.java
@@ -1,15 +1,13 @@
package lab.en2b.quizapi.auth.dtos;
import com.fasterxml.jackson.annotation.JsonProperty;
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import lombok.Setter;
+import lombok.*;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
+@EqualsAndHashCode
public class RefreshTokenResponseDto {
private String token;
diff --git a/api/src/main/java/lab/en2b/quizapi/auth/dtos/RegisterDto.java b/api/src/main/java/lab/en2b/quizapi/auth/dtos/RegisterDto.java
index 389b8583..94e474ef 100644
--- a/api/src/main/java/lab/en2b/quizapi/auth/dtos/RegisterDto.java
+++ b/api/src/main/java/lab/en2b/quizapi/auth/dtos/RegisterDto.java
@@ -1,5 +1,7 @@
package lab.en2b.quizapi.auth.dtos;
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@@ -9,10 +11,14 @@
@NoArgsConstructor
@Data
public class RegisterDto {
+ @NotBlank
@NonNull
+ @Email
private String email;
@NonNull
+ @NotBlank
private String username;
@NonNull
+ @NotBlank
private String password;
}
diff --git a/api/src/main/java/lab/en2b/quizapi/commons/user/UserService.java b/api/src/main/java/lab/en2b/quizapi/commons/user/UserService.java
index d6ed64d9..59535985 100644
--- a/api/src/main/java/lab/en2b/quizapi/commons/user/UserService.java
+++ b/api/src/main/java/lab/en2b/quizapi/commons/user/UserService.java
@@ -23,7 +23,7 @@ public class UserService implements UserDetailsService {
private final UserRepository userRepository;
private final RoleRepository roleRepository;
@Value("${REFRESH_TOKEN_DURATION_MS}")
- private Long REFRESH_TOKEN_DURATION_MS;
+ private long REFRESH_TOKEN_DURATION_MS;
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
return UserDetailsImpl.build(userRepository.findByEmail(email).orElseThrow());
diff --git a/api/src/main/java/lab/en2b/quizapi/commons/utils/TestUtils.java b/api/src/main/java/lab/en2b/quizapi/commons/utils/TestUtils.java
new file mode 100644
index 00000000..db67613e
--- /dev/null
+++ b/api/src/main/java/lab/en2b/quizapi/commons/utils/TestUtils.java
@@ -0,0 +1,13 @@
+package lab.en2b.quizapi.commons.utils;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class TestUtils {
+ public static String asJsonString(final Object obj) {
+ try {
+ return new ObjectMapper().writeValueAsString(obj);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/api/src/test/java/lab/en2b/quizapi/auth/AuthControllerTest.java b/api/src/test/java/lab/en2b/quizapi/auth/AuthControllerTest.java
new file mode 100644
index 00000000..aec0af96
--- /dev/null
+++ b/api/src/test/java/lab/en2b/quizapi/auth/AuthControllerTest.java
@@ -0,0 +1,137 @@
+package lab.en2b.quizapi.auth;
+
+import lab.en2b.quizapi.auth.config.SecurityConfig;
+import lab.en2b.quizapi.auth.dtos.LoginDto;
+import lab.en2b.quizapi.auth.dtos.RefreshTokenDto;
+import lab.en2b.quizapi.auth.dtos.RegisterDto;
+import lab.en2b.quizapi.auth.jwt.JwtUtils;
+import lab.en2b.quizapi.commons.user.UserService;
+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.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.Import;
+import org.springframework.http.ResponseEntity;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.ResultMatcher;
+
+import static lab.en2b.quizapi.commons.utils.TestUtils.asJsonString;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@WebMvcTest(AuthController.class)
+@AutoConfigureMockMvc
+@Import(SecurityConfig.class)
+public class AuthControllerTest {
+ @Autowired
+ MockMvc mockMvc;
+ @MockBean
+ AuthService authService;
+ @MockBean
+ JwtUtils jwtUtils;
+ @MockBean
+ UserService userService;
+ @Test
+ void registerUserShouldReturn200() throws Exception {
+ when(authService.register(any())).thenReturn(ResponseEntity.ok().build());
+ testRegister(asJsonString( new RegisterDto("test@email.com","test","testing"))
+ ,status().isOk());
+ }
+ @Test
+ void registerEmptyBodyShouldReturn400() throws Exception {
+ testRegister("{}",status().isBadRequest());
+ }
+ @Test
+ void registerEmptyEmailShouldReturn400() throws Exception {
+ testRegister(asJsonString( new RegisterDto("","test","testing")),
+ status().isBadRequest());
+ }
+
+ @Test
+ void registerInvalidEmailShouldReturn400() throws Exception {
+ testRegister(asJsonString( new RegisterDto("iAmAnInvalidEmail","test","testing")),
+ status().isBadRequest());
+ }
+
+ @Test
+ void registerEmptyUsernameShouldReturn400() throws Exception {
+ testRegister(asJsonString( new RegisterDto("test@email.com","","testing")),
+ status().isBadRequest());
+ }
+
+ @Test
+ void registerEmptyPasswordShouldReturn400() throws Exception {
+ testRegister(asJsonString( new RegisterDto("test@email.com","test","")),
+ status().isBadRequest());
+ }
+
+ @Test
+ void loginUserShouldReturn200() throws Exception {
+ when(authService.login(any())).thenReturn(ResponseEntity.ok().build());
+ testLogin(asJsonString( new LoginDto("test@email.com","password"))
+ ,status().isOk());
+ }
+
+ @Test
+ void loginEmptyBodyShouldReturn400() throws Exception {
+ testLogin("{}",status().isBadRequest());
+ }
+ @Test
+ void loginEmptyEmailShouldReturn400() throws Exception {
+ testLogin(asJsonString( new LoginDto("","password")),
+ status().isBadRequest());
+ }
+
+ @Test
+ void loginInvalidEmailShouldReturn400() throws Exception {
+ testLogin(asJsonString( new LoginDto("iAmAnInvalidEmail","password")),
+ status().isBadRequest());
+ }
+
+ @Test
+ void refreshTokenShouldReturn200() throws Exception {
+ when(authService.refreshToken(any())).thenReturn(ResponseEntity.ok().build());
+ testRefreshToken(asJsonString( new RefreshTokenDto("58ca95e9-c4ef-45fd-93cf-55c040aaff9c"))
+ ,status().isOk());
+ }
+
+ @Test
+ void refreshTokenEmptyBodyShouldReturn400() throws Exception {
+ when(authService.refreshToken(any())).thenReturn(ResponseEntity.ok().build());
+ testRefreshToken("{}",status().isBadRequest());
+ }
+
+ @Test
+ void refreshTokenEmptyTokenShouldReturn400() throws Exception {
+ when(authService.refreshToken(any())).thenReturn(ResponseEntity.ok().build());
+ testRefreshToken(asJsonString( new RefreshTokenDto("")), status().isBadRequest());
+ }
+
+ private void testRegister(String content, ResultMatcher code) throws Exception {
+ mockMvc.perform(post("/auth/register")
+ .content(content)
+ .contentType("application/json")
+ .with(csrf()))
+ .andExpect(code);
+ }
+
+ private void testLogin(String content, ResultMatcher code) throws Exception {
+ mockMvc.perform(post("/auth/login")
+ .content(content)
+ .contentType("application/json")
+ .with(csrf()))
+ .andExpect(code);
+ }
+
+ private void testRefreshToken(String content, ResultMatcher code) throws Exception {
+ mockMvc.perform(post("/auth/refresh-token")
+ .content(content)
+ .contentType("application/json")
+ .with(csrf()))
+ .andExpect(code);
+ }
+}
diff --git a/api/src/test/java/lab/en2b/quizapi/auth/AuthServiceTest.java b/api/src/test/java/lab/en2b/quizapi/auth/AuthServiceTest.java
new file mode 100644
index 00000000..37af25a6
--- /dev/null
+++ b/api/src/test/java/lab/en2b/quizapi/auth/AuthServiceTest.java
@@ -0,0 +1,111 @@
+package lab.en2b.quizapi.auth;
+
+import ch.qos.logback.core.util.TimeUtil;
+import lab.en2b.quizapi.auth.config.UserDetailsImpl;
+import lab.en2b.quizapi.auth.dtos.*;
+import lab.en2b.quizapi.auth.jwt.JwtUtils;
+import lab.en2b.quizapi.commons.user.User;
+import lab.en2b.quizapi.commons.user.UserRepository;
+import lab.en2b.quizapi.commons.user.UserService;
+import lab.en2b.quizapi.commons.user.role.Role;
+import lab.en2b.quizapi.commons.user.role.RoleRepository;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.core.Authentication;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import javax.swing.text.html.Option;
+import java.time.Instant;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@ExtendWith({MockitoExtension.class, SpringExtension.class})
+public class AuthServiceTest {
+ @InjectMocks
+ AuthService authService;
+ @Mock
+ UserService userService;
+ @Mock
+ UserRepository userRepository;
+ @Mock
+ RoleRepository roleRepository;
+ @Mock
+ AuthenticationManager authenticationManager;
+ @Mock
+ JwtUtils jwtUtils;
+ User defaultUser;
+ @BeforeEach
+ void setUp() {
+ this.userService = new UserService(userRepository,roleRepository);
+ this.authService = new AuthService(authenticationManager,userService,jwtUtils);
+ this.defaultUser = User.builder()
+ .id(1L)
+ .email("test@email.com")
+ .username("test")
+ .roles(Set.of(new Role("user")))
+ .password("password")
+ .refreshToken("token")
+ .refreshExpiration(Instant.ofEpochSecond(TimeUtil.computeStartOfNextSecond(System.currentTimeMillis()+ 1000)))
+ .build();
+ }
+ @Test
+ void testLogin(){
+ Authentication authentication = mock(Authentication.class);
+
+ when(authenticationManager.authenticate(any())).thenReturn(authentication);
+ when(authentication.getPrincipal()).thenReturn(UserDetailsImpl.build(defaultUser));
+ when(jwtUtils.generateJwtTokenUserPassword(authentication)).thenReturn("jwtToken");
+ when(userRepository.findById(any())).thenReturn(Optional.of(defaultUser));
+
+ ResponseEntity actual = authService.login(new LoginDto("test","password"));
+
+ assertEquals(ResponseEntity.of(Optional.of(
+ JwtResponseDto.builder()
+ .userId(1L)
+ .username(defaultUser.getUsername())
+ .email(defaultUser.getEmail())
+ .refreshToken(defaultUser.getRefreshToken())
+ .token("jwtToken")
+ .roles(List.of("user"))
+ .build()))
+ ,actual);
+
+ }
+ @Test
+ void testRegister(){
+
+ when(userRepository.existsByEmail(any())).thenReturn(false);
+ when(userRepository.existsByUsername(any())).thenReturn(false);
+ when(userRepository.save(any())).thenAnswer(i -> i.getArguments()[0]);
+ when(roleRepository.findByName(any())).thenReturn(Optional.of(new Role("user")));
+
+ ResponseEntity> actual = authService.register(new RegisterDto("test","username","password"));
+
+ assertEquals(ResponseEntity.of(Optional.of("User registered successfully!")),actual);
+
+ }
+
+ @Test
+ void testRefreshToken(){
+
+ when(userRepository.findByRefreshToken(any())).thenReturn(Optional.of(defaultUser));
+ when(jwtUtils.generateTokenFromEmail(any())).thenReturn("jwtToken");
+
+ ResponseEntity> actual = authService.refreshToken(new RefreshTokenDto("token"));
+
+ assertEquals(ResponseEntity.of(Optional.of(new RefreshTokenResponseDto("jwtToken","token"))),actual);
+
+ }
+}