-
Notifications
You must be signed in to change notification settings - Fork 80
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
…tion Feature/#1 implement user registration
- Loading branch information
Showing
13 changed files
with
431 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,4 @@ | ||
# This file is generated by the 'io.freefair.lombok' Gradle plugin | ||
config.stopBubbling = true | ||
lombok.addLombokGeneratedAnnotation = true | ||
lombok.extern.findbugs.addSuppressFBWarnings = true |
16 changes: 16 additions & 0 deletions
16
src/main/java/io/github/raeperd/realworld/application/UserLoginRequestDTO.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package io.github.raeperd.realworld.application; | ||
|
||
import lombok.Getter; | ||
|
||
@Getter | ||
public class UserLoginRequestDTO { | ||
|
||
private final String email; | ||
private final String password; | ||
|
||
public UserLoginRequestDTO(String email, String password) { | ||
this.email = email; | ||
this.password = password; | ||
} | ||
|
||
} |
23 changes: 23 additions & 0 deletions
23
src/main/java/io/github/raeperd/realworld/application/UserPostRequestDTO.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package io.github.raeperd.realworld.application; | ||
|
||
import io.github.raeperd.realworld.domain.User; | ||
import lombok.Getter; | ||
|
||
@Getter | ||
public class UserPostRequestDTO { | ||
|
||
private final String username; | ||
private final String email; | ||
private final String password; | ||
|
||
public UserPostRequestDTO(String username, String email, String password) { | ||
this.username = username; | ||
this.email = email; | ||
this.password = password; | ||
} | ||
|
||
public User toUser() { | ||
return User.createNewUser(username, email, password); | ||
} | ||
|
||
} |
33 changes: 33 additions & 0 deletions
33
src/main/java/io/github/raeperd/realworld/application/UserResponseDTO.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package io.github.raeperd.realworld.application; | ||
|
||
import com.fasterxml.jackson.annotation.JsonTypeInfo; | ||
import com.fasterxml.jackson.annotation.JsonTypeInfo.As; | ||
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id; | ||
import com.fasterxml.jackson.annotation.JsonTypeName; | ||
import io.github.raeperd.realworld.domain.User; | ||
import lombok.Getter; | ||
|
||
@JsonTypeName("user") | ||
@JsonTypeInfo(include = As.WRAPPER_OBJECT, use = Id.NAME) | ||
@Getter | ||
public class UserResponseDTO { | ||
|
||
private final String email; | ||
private final String token; | ||
private final String username; | ||
private final String bio; | ||
private final String image; | ||
|
||
public static UserResponseDTO fromUser(User user) { | ||
return new UserResponseDTO(user); | ||
} | ||
|
||
private UserResponseDTO(User user) { | ||
this.email = user.getEmail(); | ||
this.username = user.getUsername(); | ||
this.bio = user.getBio(); | ||
this.image = user.getImage(); | ||
// TODO: Generate token from user somewhere else | ||
this.token = ""; | ||
} | ||
} |
33 changes: 33 additions & 0 deletions
33
src/main/java/io/github/raeperd/realworld/application/UserRestController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package io.github.raeperd.realworld.application; | ||
|
||
import io.github.raeperd.realworld.domain.UserService; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.*; | ||
|
||
import static org.springframework.http.HttpStatus.CREATED; | ||
import static org.springframework.http.ResponseEntity.of; | ||
|
||
@RequestMapping("/users") | ||
@RestController | ||
public class UserRestController { | ||
|
||
private final UserService userService; | ||
|
||
public UserRestController(UserService userService) { | ||
this.userService = userService; | ||
} | ||
|
||
@PostMapping("/login") | ||
public ResponseEntity<UserResponseDTO> login(@RequestBody UserLoginRequestDTO loginRequest) { | ||
return of(userService.login(loginRequest.getEmail(), loginRequest.getPassword()) | ||
.map(UserResponseDTO::fromUser)); | ||
} | ||
|
||
@ResponseStatus(CREATED) | ||
@PostMapping | ||
public UserResponseDTO postUser(@RequestBody UserPostRequestDTO postRequest) { | ||
return UserResponseDTO.fromUser( | ||
userService.createUser(postRequest.toUser())); | ||
} | ||
|
||
} |
46 changes: 46 additions & 0 deletions
46
src/main/java/io/github/raeperd/realworld/domain/User.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package io.github.raeperd.realworld.domain; | ||
|
||
import javax.persistence.Entity; | ||
import javax.persistence.Id; | ||
|
||
@Entity | ||
public class User { | ||
|
||
@Id | ||
private long id; | ||
|
||
private String email; | ||
private String password; | ||
private String username; | ||
private String bio; | ||
private String image; | ||
|
||
public static User createNewUser(String username, String email, String password) { | ||
return new User(username, email, password); | ||
} | ||
|
||
private User(String username, String email, String password) { | ||
this.username = username; | ||
this.email = email; | ||
this.password = password; | ||
} | ||
|
||
protected User() { | ||
} | ||
|
||
public String getEmail() { | ||
return email; | ||
} | ||
|
||
public String getUsername() { | ||
return username; | ||
} | ||
|
||
public String getBio() { | ||
return bio; | ||
} | ||
|
||
public String getImage() { | ||
return image; | ||
} | ||
} |
15 changes: 15 additions & 0 deletions
15
src/main/java/io/github/raeperd/realworld/domain/UserRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package io.github.raeperd.realworld.domain; | ||
|
||
import org.springframework.data.jpa.repository.JpaRepository; | ||
import org.springframework.stereotype.Repository; | ||
|
||
import java.util.Optional; | ||
|
||
@Repository | ||
interface UserRepository extends JpaRepository<User, Long> { | ||
|
||
Optional<User> findFirstByEmailAndPassword(String email, String password); | ||
|
||
boolean existsByEmail(String email); | ||
|
||
} |
27 changes: 27 additions & 0 deletions
27
src/main/java/io/github/raeperd/realworld/domain/UserService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package io.github.raeperd.realworld.domain; | ||
|
||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
import java.util.Optional; | ||
|
||
@Service | ||
public class UserService { | ||
|
||
private final UserRepository userRepository; | ||
|
||
public UserService(UserRepository userRepository) { | ||
this.userRepository = userRepository; | ||
} | ||
|
||
@Transactional(readOnly = true) | ||
public Optional<User> login(String email, String password) { | ||
return userRepository.findFirstByEmailAndPassword(email, password); | ||
} | ||
|
||
@Transactional | ||
public User createUser(User user) { | ||
return userRepository.save(user); | ||
} | ||
|
||
} |
96 changes: 96 additions & 0 deletions
96
src/test/java/io/github/raeperd/realworld/application/UserRestControllerTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
package io.github.raeperd.realworld.application; | ||
|
||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import io.github.raeperd.realworld.domain.User; | ||
import io.github.raeperd.realworld.domain.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.test.web.servlet.MockMvc; | ||
|
||
import static io.github.raeperd.realworld.domain.User.createNewUser; | ||
import static java.util.Optional.of; | ||
import static org.mockito.ArgumentMatchers.any; | ||
import static org.mockito.ArgumentMatchers.anyString; | ||
import static org.mockito.BDDMockito.given; | ||
import static org.mockito.BDDMockito.then; | ||
import static org.mockito.Mockito.times; | ||
import static org.mockito.Mockito.when; | ||
import static org.springframework.http.MediaType.APPLICATION_JSON; | ||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; | ||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; | ||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; | ||
|
||
@AutoConfigureMockMvc | ||
@WebMvcTest(UserRestController.class) | ||
class UserRestControllerTest { | ||
|
||
@MockBean | ||
private UserService userService; | ||
|
||
@Autowired | ||
private MockMvc mockMvc; | ||
@Autowired | ||
private ObjectMapper objectMapper; | ||
|
||
@Test | ||
void when_post_login_expect_userService_login_called() throws Exception { | ||
final var loginDTO = new UserLoginRequestDTO("email", "password"); | ||
given(userService.login(loginDTO.getEmail(), loginDTO.getPassword())) | ||
.willReturn(of(createNewUser("", loginDTO.getEmail(), loginDTO.getPassword()))); | ||
|
||
mockMvc.perform(post("/users/login") | ||
.contentType(APPLICATION_JSON) | ||
.content(objectMapper.writeValueAsString(loginDTO))); | ||
|
||
then(userService).should(times(1)) | ||
.login(loginDTO.getEmail(), loginDTO.getPassword()); | ||
} | ||
|
||
@Test | ||
void when_post_login_expect_valid_response() throws Exception { | ||
when(userService.login(anyString(), anyString())) | ||
.thenReturn(of(createNewUser("username", "email", "password"))); | ||
|
||
mockMvc.perform(post("/users/login") | ||
.contentType(APPLICATION_JSON) | ||
.content(objectMapper.writeValueAsString(new UserLoginRequestDTO("email", "password")))) | ||
.andExpect(status().isOk()) | ||
.andExpect(jsonPath("$.user").exists()) | ||
.andExpect(jsonPath("$.user.email").exists()) | ||
.andExpect(jsonPath("$.user.bio").hasJsonPath()) | ||
.andExpect(jsonPath("$.user.image").hasJsonPath()) | ||
.andExpect(jsonPath("$.user.token").hasJsonPath()); | ||
} | ||
|
||
@Test | ||
void when_post_users_expect_userService_createUser_called() throws Exception { | ||
final var postRequestDTO = new UserPostRequestDTO("username", "email", "password"); | ||
given(userService.createUser(any(User.class))).willReturn(postRequestDTO.toUser()); | ||
|
||
mockMvc.perform(post("/users") | ||
.contentType(APPLICATION_JSON) | ||
.content(objectMapper.writeValueAsString(postRequestDTO))); | ||
|
||
then(userService).should(times(1)).createUser(any(User.class)); | ||
} | ||
|
||
@Test | ||
void when_post_users_expect_valid_response() throws Exception { | ||
when(userService.createUser(any(User.class))) | ||
.thenReturn(createNewUser("username", "email", "password")); | ||
|
||
mockMvc.perform(post("/users") | ||
.contentType(APPLICATION_JSON) | ||
.content(objectMapper.writeValueAsString(new UserLoginRequestDTO("email", "password")))) | ||
.andExpect(status().isCreated()) | ||
.andExpect(jsonPath("$.user").exists()) | ||
.andExpect(jsonPath("$.user.email").exists()) | ||
.andExpect(jsonPath("$.user.bio").hasJsonPath()) | ||
.andExpect(jsonPath("$.user.image").hasJsonPath()) | ||
.andExpect(jsonPath("$.user.token").hasJsonPath()); | ||
} | ||
|
||
} |
43 changes: 43 additions & 0 deletions
43
src/test/java/io/github/raeperd/realworld/domain/UserServiceTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package io.github.raeperd.realworld.domain; | ||
|
||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.extension.ExtendWith; | ||
import org.mockito.Mock; | ||
import org.mockito.junit.jupiter.MockitoExtension; | ||
|
||
import static org.mockito.BDDMockito.then; | ||
import static org.mockito.Mockito.times; | ||
|
||
@ExtendWith(MockitoExtension.class) | ||
class UserServiceTest { | ||
|
||
private UserService userService; | ||
|
||
@Mock | ||
private UserRepository userRepository; | ||
|
||
@BeforeEach | ||
void initializeService() { | ||
this.userService = new UserService(userRepository); | ||
} | ||
|
||
@Test | ||
void when_login_expect_userRepository_findFirstByEmailAndPassword_called() { | ||
final var email = "email"; | ||
final var password = "password"; | ||
|
||
userService.login(email, password); | ||
|
||
then(userRepository).should(times(1)) | ||
.findFirstByEmailAndPassword(email, password); | ||
} | ||
|
||
@Test | ||
void when_createUser_expect_userRepository_save_called(@Mock User user) { | ||
userService.createUser(user); | ||
|
||
then(userRepository).should(times(1)).save(user); | ||
} | ||
|
||
} |
20 changes: 20 additions & 0 deletions
20
src/test/java/io/github/raeperd/realworld/domain/UserTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package io.github.raeperd.realworld.domain; | ||
|
||
import org.junit.jupiter.api.Test; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
|
||
class UserTest { | ||
|
||
@Test | ||
void expect_user_has_protected_no_args_constructor() { | ||
class ChildUser extends User { | ||
public ChildUser() { | ||
super(); | ||
} | ||
} | ||
final var childUser = new ChildUser(); | ||
|
||
assertThat(childUser).isInstanceOf(User.class); | ||
} | ||
} |
Oops, something went wrong.