2 Commits
v0.0 ... v2.0

Author SHA1 Message Date
kok202
73453defa5 test: 실습 2부 내용 반영 2023-04-12 02:18:35 +09:00
kok202
435de08537 test: 실습 1부 내용 반영 2023-04-12 02:14:53 +09:00
83 changed files with 2705 additions and 447 deletions

View File

@@ -1,10 +1,10 @@
package com.example.demo.controller;
package com.example.demo.common.controller;
import static org.springframework.http.HttpStatus.FORBIDDEN;
import static org.springframework.http.HttpStatus.NOT_FOUND;
import com.example.demo.exception.CertificationCodeNotMatchedException;
import com.example.demo.exception.ResourceNotFoundException;
import com.example.demo.common.domain.exception.CertificationCodeNotMatchedException;
import com.example.demo.common.domain.exception.ResourceNotFoundException;
import lombok.RequiredArgsConstructor;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;

View File

@@ -1,4 +1,4 @@
package com.example.demo.controller;
package com.example.demo.common.controller;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.ResponseEntity;

View File

@@ -1,4 +1,4 @@
package com.example.demo.exception;
package com.example.demo.common.domain.exception;
public class CertificationCodeNotMatchedException extends RuntimeException {

View File

@@ -1,4 +1,4 @@
package com.example.demo.exception;
package com.example.demo.common.domain.exception;
public class ResourceNotFoundException extends RuntimeException {

View File

@@ -0,0 +1,14 @@
package com.example.demo.common.infrastructure;
import com.example.demo.common.service.port.ClockHolder;
import java.time.Clock;
import org.springframework.stereotype.Component;
@Component
public class SystemClockHolder implements ClockHolder {
@Override
public long millis() {
return Clock.systemUTC().millis();
}
}

View File

@@ -0,0 +1,14 @@
package com.example.demo.common.infrastructure;
import com.example.demo.common.service.port.UuidHolder;
import java.util.UUID;
import org.springframework.stereotype.Component;
@Component
public class SystemUuidHolder implements UuidHolder {
@Override
public String random() {
return UUID.randomUUID().toString();
}
}

View File

@@ -0,0 +1,6 @@
package com.example.demo.common.service.port;
public interface ClockHolder {
long millis();
}

View File

@@ -0,0 +1,6 @@
package com.example.demo.common.service.port;
public interface UuidHolder {
String random();
}

View File

@@ -1,49 +0,0 @@
package com.example.demo.controller;
import com.example.demo.model.dto.PostResponse;
import com.example.demo.model.dto.PostUpdateDto;
import com.example.demo.repository.PostEntity;
import com.example.demo.service.PostService;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "게시물(posts)")
@RestController
@RequestMapping("/api/posts")
@RequiredArgsConstructor
public class PostController {
private final PostService postService;
private final UserController userController;
@GetMapping("/{id}")
public ResponseEntity<PostResponse> getPostById(@PathVariable long id) {
return ResponseEntity
.ok()
.body(toResponse(postService.getPostById(id)));
}
@PutMapping("/{id}")
public ResponseEntity<PostResponse> updatePost(@PathVariable long id, @RequestBody PostUpdateDto postUpdateDto) {
return ResponseEntity
.ok()
.body(toResponse(postService.updatePost(id, postUpdateDto)));
}
public PostResponse toResponse(PostEntity postEntity) {
PostResponse PostResponse = new PostResponse();
PostResponse.setId(postEntity.getId());
PostResponse.setContent(postEntity.getContent());
PostResponse.setCreatedAt(postEntity.getCreatedAt());
PostResponse.setModifiedAt(postEntity.getModifiedAt());
PostResponse.setWriter(userController.toResponse(postEntity.getWriter()));
return PostResponse;
}
}

View File

@@ -1,97 +0,0 @@
package com.example.demo.controller;
import com.example.demo.model.dto.MyProfileResponse;
import com.example.demo.model.dto.UserResponse;
import com.example.demo.model.dto.UserUpdateDto;
import com.example.demo.repository.UserEntity;
import com.example.demo.service.UserService;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.net.URI;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "유저(users)")
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@ResponseStatus
@GetMapping("/{id}")
public ResponseEntity<UserResponse> getUserById(@PathVariable long id) {
return ResponseEntity
.ok()
.body(toResponse(userService.getByIdOrElseThrow(id)));
}
@GetMapping("/{id}/verify")
public ResponseEntity<Void> verifyEmail(
@PathVariable long id,
@RequestParam String certificationCode) {
userService.verifyEmail(id, certificationCode);
return ResponseEntity.status(HttpStatus.FOUND)
.location(URI.create("http://localhost:3000"))
.build();
}
@GetMapping("/me")
public ResponseEntity<MyProfileResponse> getMyInfo(
@Parameter(name = "EMAIL", in = ParameterIn.HEADER)
@RequestHeader("EMAIL") String email // 일반적으로 스프링 시큐리티를 사용한다면 UserPrincipal 에서 가져옵니다.
) {
UserEntity userEntity = userService.getByEmail(email);
userService.login(userEntity.getId());
return ResponseEntity
.ok()
.body(toMyProfileResponse(userEntity));
}
@PutMapping("/me")
@Parameter(in = ParameterIn.HEADER, name = "EMAIL")
public ResponseEntity<MyProfileResponse> updateMyInfo(
@Parameter(name = "EMAIL", in = ParameterIn.HEADER)
@RequestHeader("EMAIL") String email, // 일반적으로 스프링 시큐리티를 사용한다면 UserPrincipal 에서 가져옵니다.
@RequestBody UserUpdateDto userUpdateDto
) {
UserEntity userEntity = userService.getByEmail(email);
userEntity = userService.updateUser(userEntity.getId(), userUpdateDto);
return ResponseEntity
.ok()
.body(toMyProfileResponse(userEntity));
}
public UserResponse toResponse(UserEntity userEntity) {
UserResponse userResponse = new UserResponse();
userResponse.setId(userEntity.getId());
userResponse.setEmail(userEntity.getEmail());
userResponse.setNickname(userEntity.getNickname());
userResponse.setStatus(userEntity.getStatus());
userResponse.setLastLoginAt(userEntity.getLastLoginAt());
return userResponse;
}
public MyProfileResponse toMyProfileResponse(UserEntity userEntity) {
MyProfileResponse myProfileResponse = new MyProfileResponse();
myProfileResponse.setId(userEntity.getId());
myProfileResponse.setEmail(userEntity.getEmail());
myProfileResponse.setNickname(userEntity.getNickname());
myProfileResponse.setStatus(userEntity.getStatus());
myProfileResponse.setAddress(userEntity.getAddress());
myProfileResponse.setLastLoginAt(userEntity.getLastLoginAt());
return myProfileResponse;
}
}

View File

@@ -1,17 +0,0 @@
package com.example.demo.model.dto;
import com.example.demo.model.UserStatus;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class MyProfileResponse {
private Long id;
private String email;
private String nickname;
private String address;
private UserStatus status;
private Long lastLoginAt;
}

View File

@@ -1,15 +0,0 @@
package com.example.demo.model.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class PostResponse {
private Long id;
private String content;
private Long createdAt;
private Long modifiedAt;
private UserResponse writer;
}

View File

@@ -1,16 +0,0 @@
package com.example.demo.model.dto;
import com.example.demo.model.UserStatus;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class UserResponse {
private Long id;
private String email;
private String nickname;
private UserStatus status;
private Long lastLoginAt;
}

View File

@@ -0,0 +1,40 @@
package com.example.demo.post.controller;
import com.example.demo.post.controller.port.PostService;
import com.example.demo.post.controller.response.PostResponse;
import com.example.demo.post.domain.PostUpdate;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.Builder;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "게시물(posts)")
@RestController
@RequestMapping("/api/posts")
@Builder
@RequiredArgsConstructor
public class PostController {
private final PostService postService;
@GetMapping("/{id}")
public ResponseEntity<PostResponse> getById(@PathVariable long id) {
return ResponseEntity
.ok()
.body(PostResponse.from(postService.getById(id)));
}
@PutMapping("/{id}")
public ResponseEntity<PostResponse> update(@PathVariable long id, @RequestBody PostUpdate postUpdate) {
return ResponseEntity
.ok()
.body(PostResponse.from(postService.update(id, postUpdate)));
}
}

View File

@@ -1,9 +1,10 @@
package com.example.demo.controller;
package com.example.demo.post.controller;
import com.example.demo.model.dto.PostCreateDto;
import com.example.demo.model.dto.PostResponse;
import com.example.demo.service.PostService;
import com.example.demo.post.controller.port.PostService;
import com.example.demo.post.controller.response.PostResponse;
import com.example.demo.post.domain.PostCreate;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.Builder;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@@ -15,16 +16,16 @@ import org.springframework.web.bind.annotation.RestController;
@Tag(name = "게시물(posts)")
@RestController
@RequestMapping("/api/posts")
@Builder
@RequiredArgsConstructor
public class PostCreateController {
private final PostService postService;
private final PostController postController;
@PostMapping
public ResponseEntity<PostResponse> createPost(@RequestBody PostCreateDto postCreateDto) {
public ResponseEntity<PostResponse> create(@RequestBody PostCreate postCreate) {
return ResponseEntity
.status(HttpStatus.CREATED)
.body(postController.toResponse(postService.createPost(postCreateDto)));
.body(PostResponse.from(postService.create(postCreate)));
}
}

View File

@@ -0,0 +1,14 @@
package com.example.demo.post.controller.port;
import com.example.demo.post.domain.Post;
import com.example.demo.post.domain.PostCreate;
import com.example.demo.post.domain.PostUpdate;
public interface PostService {
Post getById(long id);
Post create(PostCreate postCreate);
Post update(long id, PostUpdate postUpdate);
}

View File

@@ -0,0 +1,27 @@
package com.example.demo.post.controller.response;
import com.example.demo.post.domain.Post;
import com.example.demo.user.controller.response.UserResponse;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class PostResponse {
private Long id;
private String content;
private Long createdAt;
private Long modifiedAt;
private UserResponse writer;
public static PostResponse from(Post post) {
return PostResponse.builder()
.id(post.getId())
.content(post.getContent())
.createdAt(post.getCreatedAt())
.modifiedAt(post.getModifiedAt())
.writer(UserResponse.from(post.getWriter()))
.build();
}
}

View File

@@ -0,0 +1,42 @@
package com.example.demo.post.domain;
import com.example.demo.common.service.port.ClockHolder;
import com.example.demo.user.domain.User;
import lombok.Builder;
import lombok.Getter;
@Getter
public class Post {
private final Long id;
private final String content;
private final Long createdAt;
private final Long modifiedAt;
private final User writer;
@Builder
public Post(Long id, String content, Long createdAt, Long modifiedAt, User writer) {
this.id = id;
this.content = content;
this.createdAt = createdAt;
this.modifiedAt = modifiedAt;
this.writer = writer;
}
public static Post from(User writer, PostCreate postCreate, ClockHolder clockHolder) {
return Post.builder()
.content(postCreate.getContent())
.writer(writer)
.createdAt(clockHolder.millis())
.build();
}
public Post update(PostUpdate postUpdate, ClockHolder clockHolder) {
return Post.builder()
.id(id)
.content(postUpdate.getContent())
.createdAt(createdAt)
.modifiedAt(clockHolder.millis())
.writer(writer)
.build();
}
}

View File

@@ -1,17 +1,17 @@
package com.example.demo.model.dto;
package com.example.demo.post.domain;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Getter;
@Getter
public class PostCreateDto {
public class PostCreate {
private final long writerId;
private final String content;
@Builder
public PostCreateDto(
public PostCreate(
@JsonProperty("writerId") long writerId,
@JsonProperty("content") String content) {
this.writerId = writerId;

View File

@@ -1,16 +1,16 @@
package com.example.demo.model.dto;
package com.example.demo.post.domain;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Getter;
@Getter
public class PostUpdateDto {
public class PostUpdate {
private final String content;
@Builder
public PostUpdateDto(
public PostUpdate(
@JsonProperty("content") String content) {
this.content = content;
}

View File

@@ -0,0 +1,58 @@
package com.example.demo.post.infrastructure;
import com.example.demo.post.domain.Post;
import com.example.demo.user.infrastructure.UserEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Entity
@Table(name = "posts")
public class PostEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "content")
private String content;
@Column(name = "created_at")
private Long createdAt;
@Column(name = "modified_at")
private Long modifiedAt;
@ManyToOne
@JoinColumn(name = "user_id")
private UserEntity writer;
public static PostEntity from(Post post) {
PostEntity postEntity = new PostEntity();
postEntity.id = post.getId();
postEntity.content = post.getContent();
postEntity.createdAt = post.getCreatedAt();
postEntity.modifiedAt = post.getModifiedAt();
postEntity.writer = UserEntity.from(post.getWriter());
return postEntity;
}
public Post toModel() {
return Post.builder()
.id(id)
.content(content)
.createdAt(createdAt)
.modifiedAt(modifiedAt)
.writer(writer.toModel())
.build();
}
}

View File

@@ -0,0 +1,7 @@
package com.example.demo.post.infrastructure;
import org.springframework.data.jpa.repository.JpaRepository;
public interface PostJpaRepository extends JpaRepository<PostEntity, Long> {
}

View File

@@ -0,0 +1,24 @@
package com.example.demo.post.infrastructure;
import com.example.demo.post.domain.Post;
import com.example.demo.post.service.port.PostRepository;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
@Repository
@RequiredArgsConstructor
public class PostRepositoryImpl implements PostRepository {
private final PostJpaRepository postJpaRepository;
@Override
public Optional<Post> findById(long id) {
return postJpaRepository.findById(id).map(PostEntity::toModel);
}
@Override
public Post save(Post post) {
return postJpaRepository.save(PostEntity.from(post)).toModel();
}
}

View File

@@ -0,0 +1,40 @@
package com.example.demo.post.service;
import com.example.demo.common.domain.exception.ResourceNotFoundException;
import com.example.demo.common.service.port.ClockHolder;
import com.example.demo.post.controller.port.PostService;
import com.example.demo.post.domain.Post;
import com.example.demo.post.domain.PostCreate;
import com.example.demo.post.domain.PostUpdate;
import com.example.demo.post.service.port.PostRepository;
import com.example.demo.user.domain.User;
import com.example.demo.user.service.port.UserRepository;
import lombok.Builder;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@Builder
@RequiredArgsConstructor
public class PostServiceImpl implements PostService {
private final PostRepository postRepository;
private final UserRepository userRepository;
private final ClockHolder clockHolder;
public Post getById(long id) {
return postRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Posts", id));
}
public Post create(PostCreate postCreate) {
User writer = userRepository.getById(postCreate.getWriterId());
Post post = Post.from(writer, postCreate, clockHolder);
return postRepository.save(post);
}
public Post update(long id, PostUpdate postUpdate) {
Post post = getById(id);
post = post.update(postUpdate, clockHolder);
return postRepository.save(post);
}
}

View File

@@ -0,0 +1,11 @@
package com.example.demo.post.service.port;
import com.example.demo.post.domain.Post;
import java.util.Optional;
public interface PostRepository {
Optional<Post> findById(long id);
Post save(Post post);
}

View File

@@ -1,37 +0,0 @@
package com.example.demo.repository;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Entity
@Table(name = "posts")
public class PostEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "content")
private String content;
@Column(name = "created_at")
private Long createdAt;
@Column(name = "modified_at")
private Long modifiedAt;
@ManyToOne
@JoinColumn(name = "user_id")
private UserEntity writer;
}

View File

@@ -1,7 +0,0 @@
package com.example.demo.repository;
import org.springframework.data.jpa.repository.JpaRepository;
public interface PostRepository extends JpaRepository<PostEntity, Long> {
}

View File

@@ -1,43 +0,0 @@
package com.example.demo.repository;
import com.example.demo.model.UserStatus;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Entity
@Table(name = "users")
public class UserEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "email")
private String email;
@Column(name = "nickname")
private String nickname;
@Column(name = "address")
private String address;
@Column(name = "certification_code")
private String certificationCode;
@Column(name = "status")
@Enumerated(EnumType.STRING)
private UserStatus status;
@Column(name = "last_login_at")
private Long lastLoginAt;
}

View File

@@ -1,39 +0,0 @@
package com.example.demo.service;
import com.example.demo.exception.ResourceNotFoundException;
import com.example.demo.model.dto.PostCreateDto;
import com.example.demo.model.dto.PostUpdateDto;
import com.example.demo.repository.PostEntity;
import com.example.demo.repository.PostRepository;
import com.example.demo.repository.UserEntity;
import java.time.Clock;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class PostService {
private final PostRepository postRepository;
private final UserService userService;
public PostEntity getPostById(long id) {
return postRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Posts", id));
}
public PostEntity createPost(PostCreateDto postCreateDto) {
UserEntity userEntity = userService.getByIdOrElseThrow(postCreateDto.getWriterId());
PostEntity postEntity = new PostEntity();
postEntity.setWriter(userEntity);
postEntity.setContent(postCreateDto.getContent());
postEntity.setCreatedAt(Clock.systemUTC().millis());
return postRepository.save(postEntity);
}
public PostEntity updatePost(long id, PostUpdateDto postUpdateDto) {
PostEntity postEntity = getPostById(id);
postEntity.setContent(postUpdateDto.getContent());
postEntity.setModifiedAt(Clock.systemUTC().millis());
return postRepository.save(postEntity);
}
}

View File

@@ -1,89 +0,0 @@
package com.example.demo.service;
import com.example.demo.exception.CertificationCodeNotMatchedException;
import com.example.demo.exception.ResourceNotFoundException;
import com.example.demo.model.UserStatus;
import com.example.demo.model.dto.UserCreateDto;
import com.example.demo.model.dto.UserUpdateDto;
import com.example.demo.repository.UserEntity;
import com.example.demo.repository.UserRepository;
import java.time.Clock;
import java.util.Optional;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final JavaMailSender mailSender;
public Optional<UserEntity> getById(long id) {
return userRepository.findByIdAndStatus(id, UserStatus.ACTIVE);
}
public UserEntity getByEmail(String email) {
return userRepository.findByEmailAndStatus(email, UserStatus.ACTIVE)
.orElseThrow(() -> new ResourceNotFoundException("Users", email));
}
public UserEntity getByIdOrElseThrow(long id) {
return userRepository.findByIdAndStatus(id, UserStatus.ACTIVE)
.orElseThrow(() -> new ResourceNotFoundException("Users", id));
}
@Transactional
public UserEntity createUser(UserCreateDto userCreateDto) {
UserEntity userEntity = new UserEntity();
userEntity.setEmail(userCreateDto.getEmail());
userEntity.setNickname(userCreateDto.getNickname());
userEntity.setAddress(userCreateDto.getAddress());
userEntity.setStatus(UserStatus.PENDING);
userEntity.setCertificationCode(UUID.randomUUID().toString());
userEntity = userRepository.save(userEntity);
String certificationUrl = generateCertificationUrl(userEntity);
sendCertificationEmail(userCreateDto.getEmail(), certificationUrl);
return userEntity;
}
@Transactional
public UserEntity updateUser(long id, UserUpdateDto userUpdateDto) {
UserEntity userEntity = getByIdOrElseThrow(id);
userEntity.setNickname(userUpdateDto.getNickname());
userEntity.setAddress(userUpdateDto.getAddress());
userEntity = userRepository.save(userEntity);
return userEntity;
}
@Transactional
public void login(long id) {
UserEntity userEntity = userRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Users", id));
userEntity.setLastLoginAt(Clock.systemUTC().millis());
}
@Transactional
public void verifyEmail(long id, String certificationCode) {
UserEntity userEntity = userRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Users", id));
if (!certificationCode.equals(userEntity.getCertificationCode())) {
throw new CertificationCodeNotMatchedException();
}
userEntity.setStatus(UserStatus.ACTIVE);
}
private void sendCertificationEmail(String email, String certificationUrl) {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(email);
message.setSubject("Please certify your email address");
message.setText("Please click the following link to certify your email address: " + certificationUrl);
mailSender.send(message);
}
private String generateCertificationUrl(UserEntity userEntity) {
return "http://localhost:8080/api/users/" + userEntity.getId() + "/verify?certificationCode=" + userEntity.getCertificationCode();
}
}

View File

@@ -0,0 +1,55 @@
package com.example.demo.user.controller;
import com.example.demo.user.controller.port.UserService;
import com.example.demo.user.controller.response.MyProfileResponse;
import com.example.demo.user.domain.User;
import com.example.demo.user.domain.UserUpdate;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.Builder;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "유저(users)")
@RestController
@RequestMapping("/api/users")
@Builder
@RequiredArgsConstructor
public class MyInfoController {
private final UserService userService;
@GetMapping("/me")
public ResponseEntity<MyProfileResponse> get(
@Parameter(name = "EMAIL", in = ParameterIn.HEADER)
@RequestHeader("EMAIL") String email // 일반적으로 스프링 시큐리티를 사용한다면 UserPrincipal 에서 가져옵니다.
) {
User user = userService.getByEmail(email);
userService.login(user.getId());
user = userService.getByEmail(email);
return ResponseEntity
.ok()
.body(MyProfileResponse.from(user));
}
@PutMapping("/me")
@Parameter(in = ParameterIn.HEADER, name = "EMAIL")
public ResponseEntity<MyProfileResponse> update(
@Parameter(name = "EMAIL", in = ParameterIn.HEADER)
@RequestHeader("EMAIL") String email, // 일반적으로 스프링 시큐리티를 사용한다면 UserPrincipal 에서 가져옵니다.
@RequestBody UserUpdate userUpdate
) {
User user = userService.getByEmail(email);
user = userService.update(user.getId(), userUpdate);
return ResponseEntity
.ok()
.body(MyProfileResponse.from(user));
}
}

View File

@@ -0,0 +1,45 @@
package com.example.demo.user.controller;
import com.example.demo.user.controller.port.UserService;
import com.example.demo.user.controller.response.UserResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.net.URI;
import lombok.Builder;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "유저(users)")
@RestController
@RequestMapping("/api/users")
@Builder
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@ResponseStatus
@GetMapping("/{id}")
public ResponseEntity<UserResponse> getById(@PathVariable long id) {
return ResponseEntity
.ok()
.body(UserResponse.from(userService.getById(id)));
}
@GetMapping("/{id}/verify")
public ResponseEntity<Void> verifyEmail(
@PathVariable long id,
@RequestParam String certificationCode) {
userService.verifyEmail(id, certificationCode);
return ResponseEntity.status(HttpStatus.FOUND)
.location(URI.create("http://localhost:3000"))
.build();
}
}

View File

@@ -1,10 +1,11 @@
package com.example.demo.controller;
package com.example.demo.user.controller;
import com.example.demo.model.dto.UserCreateDto;
import com.example.demo.model.dto.UserResponse;
import com.example.demo.repository.UserEntity;
import com.example.demo.service.UserService;
import com.example.demo.user.controller.port.UserService;
import com.example.demo.user.controller.response.UserResponse;
import com.example.demo.user.domain.User;
import com.example.demo.user.domain.UserCreate;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.Builder;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@@ -16,18 +17,18 @@ import org.springframework.web.bind.annotation.RestController;
@Tag(name = "유저(users)")
@RestController
@RequestMapping("/api/users")
@Builder
@RequiredArgsConstructor
public class UserCreateController {
private final UserController userController;
private final UserService userService;
@PostMapping
public ResponseEntity<UserResponse> createUser(@RequestBody UserCreateDto userCreateDto) {
UserEntity userEntity = userService.createUser(userCreateDto);
public ResponseEntity<UserResponse> create(@RequestBody UserCreate userCreate) {
User user = userService.create(userCreate);
return ResponseEntity
.status(HttpStatus.CREATED)
.body(userController.toResponse(userEntity));
.body(UserResponse.from(user));
}
}

View File

@@ -0,0 +1,20 @@
package com.example.demo.user.controller.port;
import com.example.demo.user.domain.User;
import com.example.demo.user.domain.UserCreate;
import com.example.demo.user.domain.UserUpdate;
public interface UserService {
User getByEmail(String email);
User getById(long id);
User create(UserCreate userCreate);
User update(long id, UserUpdate userUpdate);
void login(long id);
void verifyEmail(long id, String certificationCode);
}

View File

@@ -0,0 +1,29 @@
package com.example.demo.user.controller.response;
import com.example.demo.user.domain.User;
import com.example.demo.user.domain.UserStatus;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class MyProfileResponse {
private Long id;
private String email;
private String nickname;
private String address;
private UserStatus status;
private Long lastLoginAt;
public static MyProfileResponse from(User user) {
return MyProfileResponse.builder()
.id(user.getId())
.email(user.getEmail())
.nickname(user.getNickname())
.address(user.getAddress())
.status(user.getStatus())
.lastLoginAt(user.getLastLoginAt())
.build();
}
}

View File

@@ -0,0 +1,27 @@
package com.example.demo.user.controller.response;
import com.example.demo.user.domain.User;
import com.example.demo.user.domain.UserStatus;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class UserResponse {
private Long id;
private String email;
private String nickname;
private UserStatus status;
private Long lastLoginAt;
public static UserResponse from(User user) {
return UserResponse.builder()
.id(user.getId())
.email(user.getEmail())
.nickname(user.getNickname())
.status(user.getStatus())
.lastLoginAt(user.getLastLoginAt())
.build();
}
}

View File

@@ -0,0 +1,81 @@
package com.example.demo.user.domain;
import com.example.demo.common.domain.exception.CertificationCodeNotMatchedException;
import com.example.demo.common.service.port.ClockHolder;
import com.example.demo.common.service.port.UuidHolder;
import java.time.Clock;
import java.util.UUID;
import lombok.Builder;
import lombok.Getter;
@Getter
public class User {
private final Long id;
private final String email;
private final String nickname;
private final String address;
private final String certificationCode;
private final UserStatus status;
private final Long lastLoginAt;
@Builder
public User(Long id, String email, String nickname, String address, String certificationCode, UserStatus status, Long lastLoginAt) {
this.id = id;
this.email = email;
this.nickname = nickname;
this.address = address;
this.certificationCode = certificationCode;
this.status = status;
this.lastLoginAt = lastLoginAt;
}
public static User from(UserCreate userCreate, UuidHolder uuidHolder) {
return User.builder()
.email(userCreate.getEmail())
.nickname(userCreate.getNickname())
.address(userCreate.getAddress())
.status(UserStatus.PENDING)
.certificationCode(uuidHolder.random())
.build();
}
public User update(UserUpdate userUpdate) {
return User.builder()
.id(id)
.email(email)
.nickname(userUpdate.getNickname())
.address(userUpdate.getAddress())
.certificationCode(certificationCode)
.status(status)
.lastLoginAt(lastLoginAt)
.build();
}
public User login(ClockHolder clockHolder) {
return User.builder()
.id(id)
.email(email)
.nickname(nickname)
.address(address)
.certificationCode(certificationCode)
.status(status)
.lastLoginAt(clockHolder.millis())
.build();
}
public User certificate(String certificationCode) {
if (!this.certificationCode.equals(certificationCode)) {
throw new CertificationCodeNotMatchedException();
}
return User.builder()
.id(id)
.email(email)
.nickname(nickname)
.address(address)
.certificationCode(certificationCode)
.status(UserStatus.ACTIVE)
.lastLoginAt(lastLoginAt)
.build();
}
}

View File

@@ -1,18 +1,18 @@
package com.example.demo.model.dto;
package com.example.demo.user.domain;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Getter;
@Getter
public class UserCreateDto {
public class UserCreate {
private final String email;
private final String nickname;
private final String address;
@Builder
public UserCreateDto(
public UserCreate(
@JsonProperty("email") String email,
@JsonProperty("nickname") String nickname,
@JsonProperty("address") String address) {

View File

@@ -1,4 +1,4 @@
package com.example.demo.model;
package com.example.demo.user.domain;
public enum UserStatus {
PENDING, INACTIVE, ACTIVE

View File

@@ -1,17 +1,17 @@
package com.example.demo.model.dto;
package com.example.demo.user.domain;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Getter;
@Getter
public class UserUpdateDto {
public class UserUpdate {
private final String nickname;
private final String address;
@Builder
public UserUpdateDto(
public UserUpdate(
@JsonProperty("nickname") String nickname,
@JsonProperty("address") String address) {
this.nickname = nickname;

View File

@@ -0,0 +1,23 @@
package com.example.demo.user.infrastructure;
import com.example.demo.user.service.port.MailSender;
import lombok.RequiredArgsConstructor;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class MailSenderImpl implements MailSender {
private final JavaMailSender javaMailSender;
@Override
public void send(String email, String title, String content) {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(email);
message.setSubject(title);
message.setText(content);
javaMailSender.send(message);
}
}

View File

@@ -0,0 +1,68 @@
package com.example.demo.user.infrastructure;
import com.example.demo.user.domain.User;
import com.example.demo.user.domain.UserStatus;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Entity
@Table(name = "users")
public class UserEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "email")
private String email;
@Column(name = "nickname")
private String nickname;
@Column(name = "address")
private String address;
@Column(name = "certification_code")
private String certificationCode;
@Column(name = "status")
@Enumerated(EnumType.STRING)
private UserStatus status;
@Column(name = "last_login_at")
private Long lastLoginAt;
public static UserEntity from(User user) {
UserEntity userEntity = new UserEntity();
userEntity.id = user.getId();
userEntity.email = user.getEmail();
userEntity.nickname = user.getNickname();
userEntity.address = user.getAddress();
userEntity.certificationCode = user.getCertificationCode();
userEntity.status = user.getStatus();
userEntity.lastLoginAt = user.getLastLoginAt();
return userEntity;
}
public User toModel() {
return User.builder()
.id(id)
.email(email)
.nickname(nickname)
.address(address)
.certificationCode(certificationCode)
.status(status)
.lastLoginAt(lastLoginAt)
.build();
}
}

View File

@@ -1,10 +1,10 @@
package com.example.demo.repository;
package com.example.demo.user.infrastructure;
import com.example.demo.model.UserStatus;
import com.example.demo.user.domain.UserStatus;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<UserEntity, Long> {
public interface UserJpaRepository extends JpaRepository<UserEntity, Long> {
Optional<UserEntity> findByIdAndStatus(long id, UserStatus userStatus);

View File

@@ -0,0 +1,41 @@
package com.example.demo.user.infrastructure;
import com.example.demo.common.domain.exception.ResourceNotFoundException;
import com.example.demo.user.domain.User;
import com.example.demo.user.domain.UserStatus;
import com.example.demo.user.service.port.UserRepository;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
@Repository
@RequiredArgsConstructor
public class UserRepositoryImpl implements UserRepository {
private final UserJpaRepository userJpaRepository;
@Override
public User getById(long id) {
return findById(id).orElseThrow(() -> new ResourceNotFoundException("Users", id));
}
@Override
public Optional<User> findById(long id) {
return userJpaRepository.findById(id).map(UserEntity::toModel);
}
@Override
public Optional<User> findByIdAndStatus(long id, UserStatus userStatus) {
return userJpaRepository.findByIdAndStatus(id, userStatus).map(UserEntity::toModel);
}
@Override
public Optional<User> findByEmailAndStatus(String email, UserStatus userStatus) {
return userJpaRepository.findByEmailAndStatus(email, userStatus).map(UserEntity::toModel);
}
@Override
public User save(User user) {
return userJpaRepository.save(UserEntity.from(user)).toModel();
}
}

View File

@@ -0,0 +1,23 @@
package com.example.demo.user.service;
import com.example.demo.user.service.port.MailSender;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class CertificationService {
private final MailSender mailSender;
public void send(String email, long userId, String certificationCode) {
String certificationUrl = generateCertificationUrl(userId, certificationCode);
String title = "Please certify your email address";
String content = "Please click the following link to certify your email address: " + certificationUrl;
mailSender.send(email, title, content);
}
private String generateCertificationUrl(long userId, String certificationCode) {
return "http://localhost:8080/api/users/" + userId + "/verify?certificationCode=" + certificationCode;
}
}

View File

@@ -0,0 +1,66 @@
package com.example.demo.user.service;
import com.example.demo.common.domain.exception.ResourceNotFoundException;
import com.example.demo.common.service.port.ClockHolder;
import com.example.demo.common.service.port.UuidHolder;
import com.example.demo.user.controller.port.UserService;
import com.example.demo.user.domain.User;
import com.example.demo.user.domain.UserCreate;
import com.example.demo.user.domain.UserStatus;
import com.example.demo.user.domain.UserUpdate;
import com.example.demo.user.service.port.UserRepository;
import lombok.Builder;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Builder
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
private final CertificationService certificationService;
private final UuidHolder uuidHolder;
private final ClockHolder clockHolder;
public User getByEmail(String email) {
return userRepository.findByEmailAndStatus(email, UserStatus.ACTIVE)
.orElseThrow(() -> new ResourceNotFoundException("Users", email));
}
public User getById(long id) {
return userRepository.findByIdAndStatus(id, UserStatus.ACTIVE)
.orElseThrow(() -> new ResourceNotFoundException("Users", id));
}
@Transactional
public User create(UserCreate userCreate) {
User user = User.from(userCreate, uuidHolder);
user = userRepository.save(user);
certificationService.send(userCreate.getEmail(), user.getId(), user.getCertificationCode());
return user;
}
@Transactional
public User update(long id, UserUpdate userUpdate) {
User user = getById(id);
user = user.update(userUpdate);
user = userRepository.save(user);
return user;
}
@Transactional
public void login(long id) {
User user = userRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Users", id));
user = user.login(clockHolder);
userRepository.save(user);
}
@Transactional
public void verifyEmail(long id, String certificationCode) {
User user = userRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Users", id));
user = user.certificate(certificationCode);
userRepository.save(user);
}
}

View File

@@ -0,0 +1,6 @@
package com.example.demo.user.service.port;
public interface MailSender {
void send(String email, String title, String content);
}

View File

@@ -0,0 +1,18 @@
package com.example.demo.user.service.port;
import com.example.demo.user.domain.User;
import com.example.demo.user.domain.UserStatus;
import java.util.Optional;
public interface UserRepository {
User getById(long id);
Optional<User> findById(long id);
Optional<User> findByIdAndStatus(long id, UserStatus userStatus);
Optional<User> findByEmailAndStatus(String email, UserStatus userStatus);
User save(User user);
}

View File

@@ -0,0 +1,30 @@
package com.example.demo.medium;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
@SpringBootTest
@AutoConfigureMockMvc
@AutoConfigureTestDatabase
public class HealthCheckTest {
@Autowired
private MockMvc mockMvc;
@Test
void 헬스_체크_응답이_200으로_내려온다() throws Exception {
// given
// when
// then
mockMvc.perform(get("/health_check.html"))
.andExpect(status().isOk());
}
}

View File

@@ -0,0 +1,79 @@
package com.example.demo.medium;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import com.example.demo.post.domain.PostUpdate;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.Sql.ExecutionPhase;
import org.springframework.test.context.jdbc.SqlGroup;
import org.springframework.test.web.servlet.MockMvc;
@SpringBootTest
@AutoConfigureMockMvc
@AutoConfigureTestDatabase
@SqlGroup({
@Sql(value = "/sql/post-controller-test-data.sql", executionPhase = ExecutionPhase.BEFORE_TEST_METHOD),
@Sql(value = "/sql/delete-all-data.sql", executionPhase = ExecutionPhase.AFTER_TEST_METHOD)
})
public class PostControllerTest {
@Autowired
private MockMvc mockMvc;
private final ObjectMapper objectMapper = new ObjectMapper();
@Test
void 사용자는_게시물을_단건_조회_할_수_있다() throws Exception {
// given
// when
// then
mockMvc.perform(get("/api/posts/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").isNumber())
.andExpect(jsonPath("$.content").value("helloworld"))
.andExpect(jsonPath("$.writer.id").isNumber())
.andExpect(jsonPath("$.writer.email").value("kok202@naver.com"))
.andExpect(jsonPath("$.writer.nickname").value("kok202"));
}
@Test
void 사용자가_존재하지_않는_게시물을_조회할_경우_에러가_난다() throws Exception {
// given
// when
// then
mockMvc.perform(get("/api/posts/123456789"))
.andExpect(status().isNotFound())
.andExpect(content().string("Posts에서 ID 123456789를 찾을 수 없습니다."));
}
@Test
void 사용자는_게시물을_수정할_수_있다() throws Exception {
// given
PostUpdate postUpdate = PostUpdate.builder()
.content("foobar")
.build();
// when
// then
mockMvc.perform(
put("/api/posts/1")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(postUpdate)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").isNumber())
.andExpect(jsonPath("$.content").value("foobar"))
.andExpect(jsonPath("$.writer.id").isNumber())
.andExpect(jsonPath("$.writer.email").value("kok202@naver.com"))
.andExpect(jsonPath("$.writer.nickname").value("kok202"));
}
}

View File

@@ -0,0 +1,54 @@
package com.example.demo.medium;
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;
import com.example.demo.post.domain.PostCreate;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.Sql.ExecutionPhase;
import org.springframework.test.context.jdbc.SqlGroup;
import org.springframework.test.web.servlet.MockMvc;
@SpringBootTest
@AutoConfigureMockMvc
@AutoConfigureTestDatabase
@SqlGroup({
@Sql(value = "/sql/post-create-controller-test-data.sql", executionPhase = ExecutionPhase.BEFORE_TEST_METHOD),
@Sql(value = "/sql/delete-all-data.sql", executionPhase = ExecutionPhase.AFTER_TEST_METHOD)
})
public class PostCreateControllerTest {
@Autowired
private MockMvc mockMvc;
private final ObjectMapper objectMapper = new ObjectMapper();
@Test
void 사용자는_게시물을_작성할_수_있다() throws Exception {
// given
PostCreate postCreate = PostCreate.builder()
.writerId(1)
.content("helloworld")
.build();
// when
// then
mockMvc.perform(
post("/api/posts")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(postCreate)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.id").isNumber())
.andExpect(jsonPath("$.content").value("helloworld"))
.andExpect(jsonPath("$.writer.id").isNumber())
.andExpect(jsonPath("$.writer.email").value("kok202@naver.com"))
.andExpect(jsonPath("$.writer.nickname").value("kok202"));
}
}

View File

@@ -0,0 +1,72 @@
package com.example.demo.medium;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import com.example.demo.post.domain.Post;
import com.example.demo.post.domain.PostCreate;
import com.example.demo.post.domain.PostUpdate;
import com.example.demo.post.service.PostServiceImpl;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.Sql.ExecutionPhase;
import org.springframework.test.context.jdbc.SqlGroup;
@SpringBootTest
@TestPropertySource("classpath:test-application.properties")
@SqlGroup({
@Sql(value = "/sql/post-service-test-data.sql", executionPhase = ExecutionPhase.BEFORE_TEST_METHOD),
@Sql(value = "/sql/delete-all-data.sql", executionPhase = ExecutionPhase.AFTER_TEST_METHOD)
})
public class PostServiceTest {
@Autowired
private PostServiceImpl postService;
@Test
void getById는_존재하는_게시물을_내려준다() {
// given
// when
Post result = postService.getById(1);
// then
assertThat(result.getContent()).isEqualTo("helloworld");
assertThat(result.getWriter().getEmail()).isEqualTo("kok202@naver.com");
}
@Test
void postCreateDto_를_이용하여_게시물을_생성할_수_있다() {
// given
PostCreate postCreate = PostCreate.builder()
.writerId(1)
.content("foobar")
.build();
// when
Post result = postService.create(postCreate);
// then
assertThat(result.getId()).isNotNull();
assertThat(result.getContent()).isEqualTo("foobar");
assertThat(result.getCreatedAt()).isGreaterThan(0);
}
@Test
void postUpdateDto_를_이용하여_게시물을_수정할_수_있다() {
// given
PostUpdate postUpdate = PostUpdate.builder()
.content("hello world :)")
.build();
// when
postService.update(1, postUpdate);
// then
Post post = postService.getById(1);
assertThat(post.getContent()).isEqualTo("hello world :)");
assertThat(post.getModifiedAt()).isGreaterThan(0);
}
}

View File

@@ -0,0 +1,128 @@
package com.example.demo.medium;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import com.example.demo.user.domain.UserStatus;
import com.example.demo.user.domain.UserUpdate;
import com.example.demo.user.infrastructure.UserEntity;
import com.example.demo.user.infrastructure.UserJpaRepository;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.Sql.ExecutionPhase;
import org.springframework.test.context.jdbc.SqlGroup;
import org.springframework.test.web.servlet.MockMvc;
@SpringBootTest
@AutoConfigureMockMvc
@AutoConfigureTestDatabase
@SqlGroup({
@Sql(value = "/sql/user-controller-test-data.sql", executionPhase = ExecutionPhase.BEFORE_TEST_METHOD),
@Sql(value = "/sql/delete-all-data.sql", executionPhase = ExecutionPhase.AFTER_TEST_METHOD)
})
public class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private UserJpaRepository userJpaRepository;
private final ObjectMapper objectMapper = new ObjectMapper();
@Test
void 사용자는_특정_유저의_정보를_개인정보는_소거된채_전달_받을_수_있다() throws Exception {
// given
// when
// then
mockMvc.perform(get("/api/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(1))
.andExpect(jsonPath("$.email").value("kok202@naver.com"))
.andExpect(jsonPath("$.nickname").value("kok202"))
.andExpect(jsonPath("$.address").doesNotExist())
.andExpect(jsonPath("$.status").value("ACTIVE"));
}
@Test
void 사용자는_존재하지_않는_유저의_아이디로_api_호출할_경우_404_응답을_받는다() throws Exception {
// given
// when
// then
mockMvc.perform(get("/api/users/123456789"))
.andExpect(status().isNotFound())
.andExpect(content().string("Users에서 ID 123456789를 찾을 수 없습니다."));
}
@Test
void 사용자는_인증_코드로_계정을_활성화_시킬_수_있다() throws Exception {
// given
// when
// then
mockMvc.perform(
get("/api/users/2/verify")
.queryParam("certificationCode", "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaab"))
.andExpect(status().isFound());
UserEntity userEntity = userJpaRepository.findById(1L).get();
assertThat(userEntity.getStatus()).isEqualTo(UserStatus.ACTIVE);
}
@Test
void 사용자는_인증_코드가_일치하지_않을_경우_권한_없음_에러를_내려준다() throws Exception {
// given
// when
// then
mockMvc.perform(
get("/api/users/2/verify")
.queryParam("certificationCode", "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaac"))
.andExpect(status().isForbidden());
}
@Test
void 사용자는_내_정보를_불러올_때_개인정보인_주소도_갖고_올_수_있다() throws Exception {
// given
// when
// then
mockMvc.perform(
get("/api/users/me")
.header("EMAIL", "kok202@naver.com"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(1))
.andExpect(jsonPath("$.email").value("kok202@naver.com"))
.andExpect(jsonPath("$.nickname").value("kok202"))
.andExpect(jsonPath("$.address").value("Seoul"))
.andExpect(jsonPath("$.status").value("ACTIVE"));
}
@Test
void 사용자는_내_정보를_수정할_수_있다() throws Exception {
// given
UserUpdate userUpdate = UserUpdate.builder()
.nickname("kok202-n")
.address("Pangyo")
.build();
// when
// then
mockMvc.perform(
put("/api/users/me")
.header("EMAIL", "kok202@naver.com")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(userUpdate)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(1))
.andExpect(jsonPath("$.email").value("kok202@naver.com"))
.andExpect(jsonPath("$.nickname").value("kok202-n"))
.andExpect(jsonPath("$.address").value("Pangyo"))
.andExpect(jsonPath("$.status").value("ACTIVE"));
}
}

View File

@@ -0,0 +1,62 @@
package com.example.demo.medium;
import static org.mockito.ArgumentMatchers.any;
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;
import com.example.demo.user.domain.UserCreate;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.mockito.BDDMockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.Sql.ExecutionPhase;
import org.springframework.test.context.jdbc.SqlGroup;
import org.springframework.test.web.servlet.MockMvc;
@SpringBootTest
@AutoConfigureMockMvc
@AutoConfigureTestDatabase
@SqlGroup({
@Sql(value = "/sql/delete-all-data.sql", executionPhase = ExecutionPhase.AFTER_TEST_METHOD)
})
public class UserCreateControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private JavaMailSender mailSender;
private final ObjectMapper objectMapper = new ObjectMapper();
@Test
void 사용자는_회원_가입을_할_수있고_회원가입된_사용자는_PENDING_상태이다() throws Exception {
// given
UserCreate userCreate = UserCreate.builder()
.email("kok202@kakao.com")
.nickname("kok202")
.address("Pangyo")
.build();
BDDMockito.doNothing().when(mailSender).send(any(SimpleMailMessage.class));
// when
// then
mockMvc.perform(
post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(userCreate)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.id").isNumber())
.andExpect(jsonPath("$.email").value("kok202@kakao.com"))
.andExpect(jsonPath("$.nickname").value("kok202"))
.andExpect(jsonPath("$.status").value("PENDING"));
}
}

View File

@@ -0,0 +1,65 @@
package com.example.demo.medium;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import com.example.demo.user.domain.UserStatus;
import com.example.demo.user.infrastructure.UserEntity;
import com.example.demo.user.infrastructure.UserJpaRepository;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@ExtendWith(SpringExtension.class)
@DataJpaTest(showSql = true)
@TestPropertySource("classpath:test-application.properties")
@Sql("/sql/user-repository-test-data.sql")
public class UserJpaRepositoryTest {
@Autowired
private UserJpaRepository userJpaRepository;
@Test
void findByIdAndStatus_로_유저_데이터를_찾아올_수_있다() {
// given
// when
Optional<UserEntity> result = userJpaRepository.findByIdAndStatus(1, UserStatus.ACTIVE);
// then
assertThat(result.isPresent()).isTrue();
}
@Test
void findByIdAndStatus_는_데이터가_없으면_Optional_empty_를_내려준다() {
// given
// when
Optional<UserEntity> result = userJpaRepository.findByIdAndStatus(1, UserStatus.PENDING);
// then
assertThat(result.isEmpty()).isTrue();
}
@Test
void findByEmailAndStatus_로_유저_데이터를_찾아올_수_있다() {
// given
// when
Optional<UserEntity> result = userJpaRepository.findByEmailAndStatus("kok202@naver.com", UserStatus.ACTIVE);
// then
assertThat(result.isPresent()).isTrue();
}
@Test
void findByEmailAndStatus_는_데이터가_없으면_Optional_empty_를_내려준다() {
// given
// when
Optional<UserEntity> result = userJpaRepository.findByEmailAndStatus("kok202@naver.com", UserStatus.PENDING);
// then
assertThat(result.isEmpty()).isTrue();
}
}

View File

@@ -0,0 +1,153 @@
package com.example.demo.medium;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import com.example.demo.common.domain.exception.CertificationCodeNotMatchedException;
import com.example.demo.common.domain.exception.ResourceNotFoundException;
import com.example.demo.user.domain.User;
import com.example.demo.user.domain.UserCreate;
import com.example.demo.user.domain.UserStatus;
import com.example.demo.user.domain.UserUpdate;
import com.example.demo.user.service.UserServiceImpl;
import org.junit.jupiter.api.Test;
import org.mockito.BDDMockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.Sql.ExecutionPhase;
import org.springframework.test.context.jdbc.SqlGroup;
@SpringBootTest
@TestPropertySource("classpath:test-application.properties")
@SqlGroup({
@Sql(value = "/sql/user-service-test-data.sql", executionPhase = ExecutionPhase.BEFORE_TEST_METHOD),
@Sql(value = "/sql/delete-all-data.sql", executionPhase = ExecutionPhase.AFTER_TEST_METHOD)
})
public class UserServiceTest {
@Autowired
private UserServiceImpl userService;
@MockBean
private JavaMailSender mailSender;
@Test
void getByEmail은_ACTIVE_상태인_유저를_찾아올_수_있다() {
// given
String email = "kok202@naver.com";
// when
User result = userService.getByEmail(email);
// then
assertThat(result.getNickname()).isEqualTo("kok202");
}
@Test
void getByEmail은_PENDING_상태인_유저는_찾아올_수_없다() {
// given
String email = "kok303@naver.com";
// when
// then
assertThatThrownBy(() -> {
userService.getByEmail(email);
}).isInstanceOf(ResourceNotFoundException.class);
}
@Test
void getById는_ACTIVE_상태인_유저를_찾아올_수_있다() {
// given
// when
User result = userService.getById(1);
// then
assertThat(result.getNickname()).isEqualTo("kok202");
}
@Test
void getById는_PENDING_상태인_유저는_찾아올_수_없다() {
// given
// when
// then
assertThatThrownBy(() -> {
userService.getById(2);
}).isInstanceOf(ResourceNotFoundException.class);
}
@Test
void userCreate_를_이용하여_유저를_생성할_수_있다() {
// given
UserCreate userCreate = UserCreate.builder()
.email("kok202@kakao.com")
.address("Gyeongi")
.nickname("kok202-k")
.build();
BDDMockito.doNothing().when(mailSender).send(any(SimpleMailMessage.class));
// when
User result = userService.create(userCreate);
// then
assertThat(result.getId()).isNotNull();
assertThat(result.getStatus()).isEqualTo(UserStatus.PENDING);
// assertThat(result.getCertificationCode()).isEqualTo("T.T"); // FIXME
}
@Test
void userUpdateDto_를_이용하여_유저를_수정할_수_있다() {
// given
UserUpdate userUpdate = UserUpdate.builder()
.address("Incheon")
.nickname("kok202-n")
.build();
// when
userService.update(1, userUpdate);
// then
User user = userService.getById(1);
assertThat(user.getId()).isNotNull();
assertThat(user.getAddress()).isEqualTo("Incheon");
assertThat(user.getNickname()).isEqualTo("kok202-n");
}
@Test
void user를_로그인_시키면_마지막_로그인_시간이_변경된다() {
// given
// when
userService.login(1);
// then
User user = userService.getById(1);
assertThat(user.getLastLoginAt()).isGreaterThan(0L);
// assertThat(result.getLastLoginAt()).isEqualTo("T.T"); // FIXME
}
@Test
void PENDING_상태의_사용자는_인증_코드로_ACTIVE_시킬_수_있다() {
// given
// when
userService.verifyEmail(2, "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaab");
// then
User user = userService.getById(2);
assertThat(user.getStatus()).isEqualTo(UserStatus.ACTIVE);
}
@Test
void PENDING_상태의_사용자는_잘못된_인증_코드를_받으면_에러를_던진다() {
// given
// when
// then
assertThatThrownBy(() -> {
userService.verifyEmail(2, "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaac");
}).isInstanceOf(CertificationCodeNotMatchedException.class);
}
}

View File

@@ -0,0 +1,17 @@
package com.example.demo.mock;
import com.example.demo.user.service.port.MailSender;
public class FakeMailSender implements MailSender {
public String email;
public String title;
public String content;
@Override
public void send(String email, String title, String content) {
this.email = email;
this.title = title;
this.content = content;
}
}

View File

@@ -0,0 +1,40 @@
package com.example.demo.mock;
import com.example.demo.post.domain.Post;
import com.example.demo.post.service.port.PostRepository;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicLong;
public class FakePostRepository implements PostRepository {
private final AtomicLong autoGeneratedId = new AtomicLong(0);
private final List<Post> data = Collections.synchronizedList(new ArrayList<>());
@Override
public Optional<Post> findById(long id) {
return data.stream().filter(item -> item.getId().equals(id)).findAny();
}
@Override
public Post save(Post post) {
if (post.getId() == null || post.getId() == 0) {
Post newPost = Post.builder()
.id(autoGeneratedId.incrementAndGet())
.content(post.getContent())
.createdAt(post.getCreatedAt())
.modifiedAt(post.getModifiedAt())
.writer(post.getWriter())
.build();
data.add(newPost);
return newPost;
} else {
data.removeIf(item -> Objects.equals(item.getId(), post.getId()));
data.add(post);
return post;
}
}
}

View File

@@ -0,0 +1,59 @@
package com.example.demo.mock;
import com.example.demo.common.domain.exception.ResourceNotFoundException;
import com.example.demo.user.domain.User;
import com.example.demo.user.domain.UserStatus;
import com.example.demo.user.service.port.UserRepository;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicLong;
public class FakeUserRepository implements UserRepository {
private final AtomicLong autoGeneratedId = new AtomicLong(0);
private final List<User> data = Collections.synchronizedList(new ArrayList<>());
@Override
public User getById(long id) {
return findById(id).orElseThrow(() -> new ResourceNotFoundException("Users", id));
}
@Override
public Optional<User> findById(long id) {
return data.stream().filter(item -> item.getId().equals(id)).findAny();
}
@Override
public Optional<User> findByIdAndStatus(long id, UserStatus userStatus) {
return data.stream().filter(item -> item.getId().equals(id) && item.getStatus() == userStatus).findAny();
}
@Override
public Optional<User> findByEmailAndStatus(String email, UserStatus userStatus) {
return data.stream().filter(item -> item.getEmail().equals(email) && item.getStatus() == userStatus).findAny();
}
@Override
public User save(User user) {
if (user.getId() == null || user.getId() == 0) {
User newUser = User.builder()
.id(autoGeneratedId.incrementAndGet())
.email(user.getEmail())
.nickname(user.getNickname())
.address(user.getAddress())
.certificationCode(user.getCertificationCode())
.status(user.getStatus())
.lastLoginAt(user.getLastLoginAt())
.build();
data.add(newUser);
return newUser;
} else {
data.removeIf(item -> Objects.equals(item.getId(), user.getId()));
data.add(user);
return user;
}
}
}

View File

@@ -0,0 +1,15 @@
package com.example.demo.mock;
import com.example.demo.common.service.port.ClockHolder;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public class TestClockHolder implements ClockHolder {
private final long millis;
@Override
public long millis() {
return millis;
}
}

View File

@@ -0,0 +1,65 @@
package com.example.demo.mock;
import com.example.demo.common.service.port.ClockHolder;
import com.example.demo.common.service.port.UuidHolder;
import com.example.demo.post.controller.PostController;
import com.example.demo.post.controller.PostCreateController;
import com.example.demo.post.controller.port.PostService;
import com.example.demo.post.service.PostServiceImpl;
import com.example.demo.post.service.port.PostRepository;
import com.example.demo.user.controller.MyInfoController;
import com.example.demo.user.controller.UserController;
import com.example.demo.user.controller.UserCreateController;
import com.example.demo.user.service.CertificationService;
import com.example.demo.user.service.UserServiceImpl;
import com.example.demo.user.service.port.MailSender;
import com.example.demo.user.service.port.UserRepository;
import lombok.Builder;
public class TestContainer {
public final MailSender mailSender;
public final UserRepository userRepository;
public final PostRepository postRepository;
public final PostService postService;
public final CertificationService certificationService;
public final UserController userController;
public final MyInfoController myInfoController;
public final UserCreateController userCreateController;
public final PostController postController;
public final PostCreateController postCreateController;
@Builder
public TestContainer(ClockHolder clockHolder, UuidHolder uuidHolder) {
this.mailSender = new FakeMailSender();
this.userRepository = new FakeUserRepository();
this.postRepository = new FakePostRepository();
this.postService = PostServiceImpl.builder()
.postRepository(this.postRepository)
.userRepository(this.userRepository)
.clockHolder(clockHolder)
.build();
this.certificationService = new CertificationService(this.mailSender);
UserServiceImpl userService = UserServiceImpl.builder()
.uuidHolder(uuidHolder)
.clockHolder(clockHolder)
.userRepository(this.userRepository)
.certificationService(this.certificationService)
.build();
this.userController = UserController.builder()
.userService(userService)
.build();
this.myInfoController = MyInfoController.builder()
.userService(userService)
.build();
this.userCreateController = UserCreateController.builder()
.userService(userService)
.build();
this.postController = PostController.builder()
.postService(postService)
.build();
this.postCreateController = PostCreateController.builder()
.postService(postService)
.build();
}
}

View File

@@ -0,0 +1,15 @@
package com.example.demo.mock;
import com.example.demo.common.service.port.UuidHolder;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public class TestUuidHolder implements UuidHolder {
private final String uuid;
@Override
public String random() {
return uuid;
}
}

View File

@@ -0,0 +1,104 @@
package com.example.demo.post.controller;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import com.example.demo.common.domain.exception.ResourceNotFoundException;
import com.example.demo.mock.TestContainer;
import com.example.demo.post.controller.response.PostResponse;
import com.example.demo.post.domain.Post;
import com.example.demo.post.domain.PostUpdate;
import com.example.demo.user.domain.User;
import com.example.demo.user.domain.UserStatus;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseEntity;
public class PostControllerTest {
@Test
void 사용자는_게시물을_단건_조회_할_수_있다() {
// given
TestContainer testContainer = TestContainer.builder()
.build();
User user = User.builder()
.id(1L)
.email("kok202@naver.com")
.nickname("kok202")
.address("Seoul")
.status(UserStatus.ACTIVE)
.certificationCode("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaab")
.lastLoginAt(100L)
.build();
testContainer.userRepository.save(user);
testContainer.postRepository.save(Post.builder()
.id(1L)
.content("helloworld")
.writer(user)
.createdAt(100L)
.build());
// when
ResponseEntity<PostResponse> result = testContainer.postController.getById(1L);
// then
assertThat(result.getStatusCode()).isEqualTo(HttpStatusCode.valueOf(200));
assertThat(result.getBody()).isNotNull();
assertThat(result.getBody().getContent()).isEqualTo("helloworld");
assertThat(result.getBody().getWriter().getNickname()).isEqualTo("kok202");
assertThat(result.getBody().getCreatedAt()).isEqualTo(100L);
}
@Test
void 사용자가_존재하지_않는_게시물을_조회할_경우_에러가_난다() throws Exception {
// given
TestContainer testContainer = TestContainer.builder()
.build();
// when
// then
assertThatThrownBy(() -> {
testContainer.postController.getById(1);
}).isInstanceOf(ResourceNotFoundException.class);
}
@Test
void 사용자는_게시물을_수정할_수_있다() {
// given
TestContainer testContainer = TestContainer.builder()
.clockHolder(() -> 200L)
.build();
User user = User.builder()
.id(1L)
.email("kok202@naver.com")
.nickname("kok202")
.address("Seoul")
.status(UserStatus.ACTIVE)
.certificationCode("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaab")
.lastLoginAt(100L)
.build();
testContainer.userRepository.save(user);
testContainer.postRepository.save(Post.builder()
.id(1L)
.content("helloworld")
.writer(user)
.createdAt(100L)
.build());
// when
ResponseEntity<PostResponse> result = testContainer.postController.update(1L, PostUpdate.builder()
.content("foobar")
.build());
// then
assertThat(result.getStatusCode()).isEqualTo(HttpStatusCode.valueOf(200));
assertThat(result.getBody()).isNotNull();
assertThat(result.getBody().getContent()).isEqualTo("foobar");
assertThat(result.getBody().getWriter().getNickname()).isEqualTo("kok202");
assertThat(result.getBody().getCreatedAt()).isEqualTo(100L);
assertThat(result.getBody().getModifiedAt()).isEqualTo(200L);
}
}

View File

@@ -0,0 +1,48 @@
package com.example.demo.post.controller;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import com.example.demo.mock.TestContainer;
import com.example.demo.post.controller.response.PostResponse;
import com.example.demo.post.domain.PostCreate;
import com.example.demo.user.domain.User;
import com.example.demo.user.domain.UserStatus;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseEntity;
public class PostCreateControllerTest {
@Test
void 사용자는_게시물을_작성할_수_있다() {
// given
TestContainer testContainer = TestContainer.builder()
.clockHolder(() -> 1679530673958L)
.build();
testContainer.userRepository.save(User.builder()
.id(1L)
.email("kok202@naver.com")
.nickname("kok202")
.address("Seoul")
.status(UserStatus.ACTIVE)
.certificationCode("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaab")
.lastLoginAt(100L)
.build());
PostCreate postCreate = PostCreate.builder()
.writerId(1)
.content("helloworld")
.build();
// when
ResponseEntity<PostResponse> result = testContainer.postCreateController.create(postCreate);
// then
assertThat(result.getStatusCode()).isEqualTo(HttpStatusCode.valueOf(201));
assertThat(result.getBody()).isNotNull();
assertThat(result.getBody().getContent()).isEqualTo("helloworld");
assertThat(result.getBody().getWriter().getNickname()).isEqualTo("kok202");
assertThat(result.getBody().getCreatedAt()).isEqualTo(1679530673958L);
}
}

View File

@@ -0,0 +1,35 @@
package com.example.demo.post.controller.response;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import com.example.demo.post.domain.Post;
import com.example.demo.user.domain.User;
import com.example.demo.user.domain.UserStatus;
import org.junit.jupiter.api.Test;
public class PostResponseTest {
@Test
public void Post로_응답을_생성할_수_있다() {
// given
Post post = Post.builder()
.content("helloworld")
.writer(User.builder()
.email("kok202@naver.com")
.nickname("kok202")
.address("Seoul")
.status(UserStatus.ACTIVE)
.certificationCode("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaab")
.build())
.build();
// when
PostResponse postResponse = PostResponse.from(post);
// then
assertThat(postResponse.getContent()).isEqualTo("helloworld");
assertThat(postResponse.getWriter().getEmail()).isEqualTo("kok202@naver.com");
assertThat(postResponse.getWriter().getNickname()).isEqualTo("kok202");
assertThat(postResponse.getWriter().getStatus()).isEqualTo(UserStatus.ACTIVE);
}
}

View File

@@ -0,0 +1,69 @@
package com.example.demo.post.domain;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import com.example.demo.mock.TestClockHolder;
import com.example.demo.user.domain.User;
import com.example.demo.user.domain.UserStatus;
import org.junit.jupiter.api.Test;
public class PostTest {
@Test
public void PostCreate으로_게시물을_만들_수_있다() {
// given
PostCreate postCreate = PostCreate.builder()
.writerId(1)
.content("helloworld")
.build();
User writer = User.builder()
.id(1L)
.email("kok202@naver.com")
.nickname("kok202")
.address("Seoul")
.status(UserStatus.ACTIVE)
.certificationCode("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaab")
.build();
// when
Post post = Post.from(writer, postCreate, new TestClockHolder(1679530673958L));
// then
assertThat(post.getContent()).isEqualTo("helloworld");
assertThat(post.getCreatedAt()).isEqualTo(1679530673958L);
assertThat(post.getWriter().getEmail()).isEqualTo("kok202@naver.com");
assertThat(post.getWriter().getNickname()).isEqualTo("kok202");
assertThat(post.getWriter().getAddress()).isEqualTo("Seoul");
assertThat(post.getWriter().getStatus()).isEqualTo(UserStatus.ACTIVE);
assertThat(post.getWriter().getCertificationCode()).isEqualTo("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaab");
}
@Test
public void PostUpdate로_게시물을_수정할_수_있다() {
// given
PostUpdate postUpdate = PostUpdate.builder()
.content("foobar")
.build();
User writer = User.builder()
.id(1L)
.email("kok202@naver.com")
.nickname("kok202")
.address("Seoul")
.status(UserStatus.ACTIVE)
.certificationCode("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaab")
.build();
Post post = Post.builder()
.id(1L)
.content("helloworld")
.createdAt(1678530673958L)
.modifiedAt(0L)
.writer(writer)
.build();
// when
post = post.update(postUpdate, new TestClockHolder(1679530673958L));
// then
assertThat(post.getContent()).isEqualTo("foobar");
assertThat(post.getModifiedAt()).isEqualTo(1679530673958L);
}
}

View File

@@ -0,0 +1,102 @@
package com.example.demo.post.service;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import com.example.demo.mock.FakePostRepository;
import com.example.demo.mock.FakeUserRepository;
import com.example.demo.mock.TestClockHolder;
import com.example.demo.post.domain.Post;
import com.example.demo.post.domain.PostCreate;
import com.example.demo.post.domain.PostUpdate;
import com.example.demo.user.domain.User;
import com.example.demo.user.domain.UserStatus;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class PostServiceTest {
private PostServiceImpl postService;
@BeforeEach
void init() {
FakePostRepository fakePostRepository = new FakePostRepository();
FakeUserRepository fakeUserRepository = new FakeUserRepository();
this.postService = PostServiceImpl.builder()
.postRepository(fakePostRepository)
.userRepository(fakeUserRepository)
.clockHolder(new TestClockHolder(1679530673958L))
.build();
User user1 = User.builder()
.id(1L)
.email("kok202@naver.com")
.nickname("kok202")
.address("Seoul")
.certificationCode("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")
.status(UserStatus.ACTIVE)
.lastLoginAt(0L)
.build();
User user2 = User.builder()
.id(2L)
.email("kok303@naver.com")
.nickname("kok303")
.address("Seoul")
.certificationCode("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaab")
.status(UserStatus.PENDING)
.lastLoginAt(0L)
.build();
fakeUserRepository.save(user1);
fakeUserRepository.save(user2);
fakePostRepository.save(Post.builder()
.id(1L)
.content("helloworld")
.createdAt(1678530673958L)
.modifiedAt(0L)
.writer(user1)
.build());
}
@Test
void getById는_존재하는_게시물을_내려준다() {
// given
// when
Post result = postService.getById(1);
// then
assertThat(result.getContent()).isEqualTo("helloworld");
assertThat(result.getWriter().getEmail()).isEqualTo("kok202@naver.com");
}
@Test
void postCreateDto_를_이용하여_게시물을_생성할_수_있다() {
// given
PostCreate postCreate = PostCreate.builder()
.writerId(1)
.content("foobar")
.build();
// when
Post result = postService.create(postCreate);
// then
assertThat(result.getId()).isNotNull();
assertThat(result.getContent()).isEqualTo("foobar");
assertThat(result.getCreatedAt()).isEqualTo(1679530673958L);
}
@Test
void postUpdateDto_를_이용하여_게시물을_수정할_수_있다() {
// given
PostUpdate postUpdate = PostUpdate.builder()
.content("hello world :)")
.build();
// when
postService.update(1, postUpdate);
// then
Post post = postService.getById(1);
assertThat(post.getContent()).isEqualTo("hello world :)");
assertThat(post.getModifiedAt()).isEqualTo(1679530673958L);
}
}

View File

@@ -0,0 +1,78 @@
package com.example.demo.user.controller;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
import com.example.demo.mock.TestContainer;
import com.example.demo.user.controller.response.MyProfileResponse;
import com.example.demo.user.domain.User;
import com.example.demo.user.domain.UserStatus;
import com.example.demo.user.domain.UserUpdate;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseEntity;
public class MyInfoControllerTest {
@Test
void 사용자는_내_정보를_불러올_때_개인정보인_주소도_갖고_올_수_있다() {
// given
TestContainer testContainer = TestContainer.builder()
.clockHolder(() -> 1678530673958L)
.build();
testContainer.userRepository.save(User.builder()
.id(1L)
.email("kok202@naver.com")
.nickname("kok202")
.address("Seoul")
.status(UserStatus.ACTIVE)
.certificationCode("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaab")
.lastLoginAt(100L)
.build());
// when
ResponseEntity<MyProfileResponse> result = testContainer.myInfoController.get("kok202@naver.com");
// then
assertThat(result.getStatusCode()).isEqualTo(HttpStatusCode.valueOf(200));
assertThat(result.getBody()).isNotNull();
assertThat(result.getBody().getEmail()).isEqualTo("kok202@naver.com");
assertThat(result.getBody().getNickname()).isEqualTo("kok202");
assertThat(result.getBody().getLastLoginAt()).isEqualTo(1678530673958L);
assertThat(result.getBody().getAddress()).isEqualTo("Seoul");
assertThat(result.getBody().getStatus()).isEqualTo(UserStatus.ACTIVE);
}
@Test
void 사용자는_내_정보를_수정할_수_있다() {
// given
TestContainer testContainer = TestContainer.builder()
.build();
testContainer.userRepository.save(User.builder()
.id(1L)
.email("kok202@naver.com")
.nickname("kok202")
.address("Seoul")
.status(UserStatus.ACTIVE)
.certificationCode("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaab")
.lastLoginAt(100L)
.build());
// when
ResponseEntity<MyProfileResponse> result = testContainer.myInfoController.update("kok202@naver.com", UserUpdate.builder()
.address("Pangyo")
.nickname("kok202-n")
.build());
// then
assertThat(result.getStatusCode()).isEqualTo(HttpStatusCode.valueOf(200));
assertThat(result.getBody()).isNotNull();
assertThat(result.getBody().getEmail()).isEqualTo("kok202@naver.com");
assertThat(result.getBody().getNickname()).isEqualTo("kok202-n");
assertThat(result.getBody().getLastLoginAt()).isEqualTo(100);
assertThat(result.getBody().getAddress()).isEqualTo("Pangyo");
assertThat(result.getBody().getStatus()).isEqualTo(UserStatus.ACTIVE);
}
}

View File

@@ -0,0 +1,107 @@
package com.example.demo.user.controller;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import com.example.demo.common.domain.exception.CertificationCodeNotMatchedException;
import com.example.demo.common.domain.exception.ResourceNotFoundException;
import com.example.demo.mock.TestContainer;
import com.example.demo.user.controller.response.MyProfileResponse;
import com.example.demo.user.controller.response.UserResponse;
import com.example.demo.user.domain.User;
import com.example.demo.user.domain.UserStatus;
import com.example.demo.user.domain.UserUpdate;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseEntity;
public class UserControllerTest {
@Test
void 사용자는_특정_유저의_정보를_개인정보는_소거된채_전달_받을_수_있다() {
// given
TestContainer testContainer = TestContainer.builder()
.build();
testContainer.userRepository.save(User.builder()
.id(1L)
.email("kok202@naver.com")
.nickname("kok202")
.address("Seoul")
.status(UserStatus.ACTIVE)
.certificationCode("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaab")
.lastLoginAt(100L)
.build());
// when
ResponseEntity<UserResponse> result = testContainer.userController.getById(1);
// then
assertThat(result.getStatusCode()).isEqualTo(HttpStatusCode.valueOf(200));
assertThat(result.getBody()).isNotNull();
assertThat(result.getBody().getEmail()).isEqualTo("kok202@naver.com");
assertThat(result.getBody().getNickname()).isEqualTo("kok202");
assertThat(result.getBody().getLastLoginAt()).isEqualTo(100);
assertThat(result.getBody().getStatus()).isEqualTo(UserStatus.ACTIVE);
}
@Test
void 사용자는_존재하지_않는_유저의_아이디로_api_호출할_경우_404_응답을_받는다() {
// given
TestContainer testContainer = TestContainer.builder()
.build();
// when
// then
assertThatThrownBy(() -> {
testContainer.userController.getById(1);
}).isInstanceOf(ResourceNotFoundException.class);
}
@Test
void 사용자는_인증_코드로_계정을_활성화_시킬_수_있다() {
// given
TestContainer testContainer = TestContainer.builder()
.build();
testContainer.userRepository.save(User.builder()
.id(1L)
.email("kok202@naver.com")
.nickname("kok202")
.address("Seoul")
.status(UserStatus.PENDING)
.certificationCode("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaab")
.lastLoginAt(100L)
.build());
// when
ResponseEntity<Void> result = testContainer.userController.verifyEmail(1, "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaab");
// then
assertThat(result.getStatusCode()).isEqualTo(HttpStatusCode.valueOf(302));
assertThat(testContainer.userRepository.getById(1).getStatus()).isEqualTo(UserStatus.ACTIVE);
}
@Test
void 사용자는_인증_코드가_일치하지_않을_경우_권한_없음_에러를_내려준다() {
// given
TestContainer testContainer = TestContainer.builder()
.build();
testContainer.userRepository.save(User.builder()
.id(1L)
.email("kok202@naver.com")
.nickname("kok202")
.address("Seoul")
.status(UserStatus.PENDING)
.certificationCode("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaab")
.lastLoginAt(100L)
.build());
// when
assertThatThrownBy(() -> {
testContainer.userController.verifyEmail(1, "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaac");
}).isInstanceOf(CertificationCodeNotMatchedException.class);
}
}

View File

@@ -0,0 +1,43 @@
package com.example.demo.user.controller;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import com.example.demo.mock.TestContainer;
import com.example.demo.user.controller.response.UserResponse;
import com.example.demo.user.domain.UserCreate;
import com.example.demo.user.domain.UserStatus;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseEntity;
public class UserCreateControllerTest {
@Test
void 사용자는_회원_가입을_할_수있고_회원가입된_사용자는_PENDING_상태이다() {
// given
TestContainer testContainer = TestContainer.builder()
.uuidHolder(() -> "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaab")
.build();
UserCreate userCreate = UserCreate.builder()
.email("kok202@kakao.com")
.nickname("kok202")
.address("Pangyo")
.build();
// when
ResponseEntity<UserResponse> result = testContainer.userCreateController.create(userCreate);
// then
assertThat(result.getStatusCode()).isEqualTo(HttpStatusCode.valueOf(201));
assertThat(result.getBody()).isNotNull();
assertThat(result.getBody().getEmail()).isEqualTo("kok202@kakao.com");
assertThat(result.getBody().getNickname()).isEqualTo("kok202");
assertThat(result.getBody().getLastLoginAt()).isNull();
assertThat(result.getBody().getStatus()).isEqualTo(UserStatus.PENDING);
assertThat(testContainer.userRepository.getById(1).getCertificationCode()).isEqualTo("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaab");
}
}

View File

@@ -0,0 +1,34 @@
package com.example.demo.user.controller.response;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import com.example.demo.user.domain.User;
import com.example.demo.user.domain.UserStatus;
import org.junit.jupiter.api.Test;
public class MyProfileResponseTest {
@Test
public void User으로_응답을_생성할_수_있다() {
// given
User user = User.builder()
.id(1L)
.email("kok202@naver.com")
.nickname("kok202")
.address("Seoul")
.status(UserStatus.ACTIVE)
.lastLoginAt(100L)
.certificationCode("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaab")
.build();
// when
MyProfileResponse myProfileResponse = MyProfileResponse.from(user);
// then
assertThat(myProfileResponse.getId()).isEqualTo(1);
assertThat(myProfileResponse.getEmail()).isEqualTo("kok202@naver.com");
assertThat(myProfileResponse.getAddress()).isEqualTo("Seoul");
assertThat(myProfileResponse.getStatus()).isEqualTo(UserStatus.ACTIVE);
assertThat(myProfileResponse.getLastLoginAt()).isEqualTo(100L);
}
}

View File

@@ -0,0 +1,33 @@
package com.example.demo.user.controller.response;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import com.example.demo.user.domain.User;
import com.example.demo.user.domain.UserStatus;
import org.junit.jupiter.api.Test;
public class UserResponseTest {
@Test
public void User으로_응답을_생성할_수_있다() {
// given
User user = User.builder()
.id(1L)
.email("kok202@naver.com")
.nickname("kok202")
.address("Seoul")
.status(UserStatus.ACTIVE)
.lastLoginAt(100L)
.certificationCode("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaab")
.build();
// when
UserResponse userResponse = UserResponse.from(user);
// then
assertThat(userResponse.getId()).isEqualTo(1);
assertThat(userResponse.getEmail()).isEqualTo("kok202@naver.com");
assertThat(userResponse.getStatus()).isEqualTo(UserStatus.ACTIVE);
assertThat(userResponse.getLastLoginAt()).isEqualTo(100L);
}
}

View File

@@ -0,0 +1,122 @@
package com.example.demo.user.domain;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
import com.example.demo.common.domain.exception.CertificationCodeNotMatchedException;
import com.example.demo.mock.TestClockHolder;
import com.example.demo.mock.TestUuidHolder;
import org.junit.jupiter.api.Test;
public class UserTest {
@Test
public void UserCreate_객체로_생성할_수_있다() {
// given
UserCreate userCreate = UserCreate.builder()
.email("kok202@kakao.com")
.nickname("kok202")
.address("Pangyo")
.build();
// when
User user = User.from(userCreate, new TestUuidHolder("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"));
// then
assertThat(user.getId()).isNull();
assertThat(user.getEmail()).isEqualTo("kok202@kakao.com");
assertThat(user.getNickname()).isEqualTo("kok202");
assertThat(user.getAddress()).isEqualTo("Pangyo");
assertThat(user.getStatus()).isEqualTo(UserStatus.PENDING);
assertThat(user.getCertificationCode()).isEqualTo("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa");
}
@Test
public void UserUpdate_객체로_데이터를_업데이트_할_수_있다() {
// given
User user = User.builder()
.id(1L)
.email("kok202@kakao.com")
.nickname("kok202")
.address("Seoul")
.status(UserStatus.ACTIVE)
.lastLoginAt(100L)
.certificationCode("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")
.build();
UserUpdate userUpdate = UserUpdate.builder()
.nickname("kok202-k")
.address("Pangyo")
.build();
// when
user = user.update(userUpdate);
// then
assertThat(user.getId()).isEqualTo(1L);
assertThat(user.getEmail()).isEqualTo("kok202@kakao.com");
assertThat(user.getNickname()).isEqualTo("kok202-k");
assertThat(user.getAddress()).isEqualTo("Pangyo");
assertThat(user.getStatus()).isEqualTo(UserStatus.ACTIVE);
assertThat(user.getCertificationCode()).isEqualTo("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa");
assertThat(user.getLastLoginAt()).isEqualTo(100L);
}
@Test
public void 로그인을_할_수_있고_로그인시_마지막_로그인_시간이_변경된다() {
// given
User user = User.builder()
.id(1L)
.email("kok202@kakao.com")
.nickname("kok202")
.address("Seoul")
.status(UserStatus.ACTIVE)
.lastLoginAt(100L)
.certificationCode("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")
.build();
// when
user = user.login(new TestClockHolder(1678530673958L));
// then
assertThat(user.getLastLoginAt()).isEqualTo(1678530673958L);
}
@Test
public void 유효한_인증_코드로_계정을_활성화_할_수_있다() {
// given
User user = User.builder()
.id(1L)
.email("kok202@kakao.com")
.nickname("kok202")
.address("Seoul")
.status(UserStatus.PENDING)
.lastLoginAt(100L)
.certificationCode("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")
.build();
// when
user = user.certificate("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa");
// then
assertThat(user.getStatus()).isEqualTo(UserStatus.ACTIVE);
}
@Test
public void 잘못된_인증_코드로_계정을_활성화_하려하면_에러를_던진다() {
// given
User user = User.builder()
.id(1L)
.email("kok202@kakao.com")
.nickname("kok202")
.address("Seoul")
.status(UserStatus.PENDING)
.lastLoginAt(100L)
.certificationCode("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")
.build();
// when
// then
assertThatThrownBy(() -> user.certificate("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaab"))
.isInstanceOf(CertificationCodeNotMatchedException.class);
}
}

View File

@@ -0,0 +1,25 @@
package com.example.demo.user.service;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import com.example.demo.mock.FakeMailSender;
import org.junit.jupiter.api.Test;
public class CertificationServiceTest {
@Test
public void 이메일과_컨텐츠가_제대로_만들어져서_보내지는지_테스트한다() {
// given
FakeMailSender fakeMailSender = new FakeMailSender();
CertificationService certificationService = new CertificationService(fakeMailSender);
// when
certificationService.send("kok202@naver.com", 1, "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa");
// then
assertThat(fakeMailSender.email).isEqualTo("kok202@naver.com");
assertThat(fakeMailSender.title).isEqualTo("Please certify your email address");
assertThat(fakeMailSender.content).isEqualTo(
"Please click the following link to certify your email address: http://localhost:8080/api/users/1/verify?certificationCode=aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa");
}
}

View File

@@ -0,0 +1,166 @@
package com.example.demo.user.service;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import com.example.demo.common.domain.exception.CertificationCodeNotMatchedException;
import com.example.demo.common.domain.exception.ResourceNotFoundException;
import com.example.demo.mock.FakeMailSender;
import com.example.demo.mock.FakeUserRepository;
import com.example.demo.mock.TestClockHolder;
import com.example.demo.mock.TestUuidHolder;
import com.example.demo.user.domain.User;
import com.example.demo.user.domain.UserCreate;
import com.example.demo.user.domain.UserStatus;
import com.example.demo.user.domain.UserUpdate;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class UserServiceTest {
private UserServiceImpl userService;
@BeforeEach
void init() {
FakeMailSender fakeMailSender = new FakeMailSender();
FakeUserRepository fakeUserRepository = new FakeUserRepository();
this.userService = UserServiceImpl.builder()
.uuidHolder(new TestUuidHolder("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaab"))
.clockHolder(new TestClockHolder(1678530673958L))
.userRepository(fakeUserRepository)
.certificationService(new CertificationService(fakeMailSender))
.build();
fakeUserRepository.save(User.builder()
.id(1L)
.email("kok202@naver.com")
.nickname("kok202")
.address("Seoul")
.certificationCode("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")
.status(UserStatus.ACTIVE)
.lastLoginAt(0L)
.build());
fakeUserRepository.save(User.builder()
.id(2L)
.email("kok303@naver.com")
.nickname("kok303")
.address("Seoul")
.certificationCode("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaab")
.status(UserStatus.PENDING)
.lastLoginAt(0L)
.build());
}
@Test
void getByEmail은_ACTIVE_상태인_유저를_찾아올_수_있다() {
// given
String email = "kok202@naver.com";
// when
User result = userService.getByEmail(email);
// then
assertThat(result.getNickname()).isEqualTo("kok202");
}
@Test
void getByEmail은_PENDING_상태인_유저는_찾아올_수_없다() {
// given
String email = "kok303@naver.com";
// when
// then
assertThatThrownBy(() -> {
userService.getByEmail(email);
}).isInstanceOf(ResourceNotFoundException.class);
}
@Test
void getById는_ACTIVE_상태인_유저를_찾아올_수_있다() {
// given
// when
User result = userService.getById(1);
// then
assertThat(result.getNickname()).isEqualTo("kok202");
}
@Test
void getById는_PENDING_상태인_유저는_찾아올_수_없다() {
// given
// when
// then
assertThatThrownBy(() -> {
userService.getById(2);
}).isInstanceOf(ResourceNotFoundException.class);
}
@Test
void userCreate_를_이용하여_유저를_생성할_수_있다() {
// given
UserCreate userCreate = UserCreate.builder()
.email("kok202@kakao.com")
.address("Gyeongi")
.nickname("kok202-k")
.build();
// when
User result = userService.create(userCreate);
// then
assertThat(result.getId()).isNotNull();
assertThat(result.getStatus()).isEqualTo(UserStatus.PENDING);
assertThat(result.getCertificationCode()).isEqualTo("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaab");
}
@Test
void userUpdateDto_를_이용하여_유저를_수정할_수_있다() {
// given
UserUpdate userUpdate = UserUpdate.builder()
.address("Incheon")
.nickname("kok202-n")
.build();
// when
userService.update(1, userUpdate);
// then
User user = userService.getById(1);
assertThat(user.getId()).isNotNull();
assertThat(user.getAddress()).isEqualTo("Incheon");
assertThat(user.getNickname()).isEqualTo("kok202-n");
}
@Test
void user를_로그인_시키면_마지막_로그인_시간이_변경된다() {
// given
// when
userService.login(1);
// then
User user = userService.getById(1);
assertThat(user.getLastLoginAt()).isEqualTo(1678530673958L);
}
@Test
void PENDING_상태의_사용자는_인증_코드로_ACTIVE_시킬_수_있다() {
// given
// when
userService.verifyEmail(2, "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaab");
// then
User user = userService.getById(2);
assertThat(user.getStatus()).isEqualTo(UserStatus.ACTIVE);
}
@Test
void PENDING_상태의_사용자는_잘못된_인증_코드를_받으면_에러를_던진다() {
// given
// when
// then
assertThatThrownBy(() -> {
userService.verifyEmail(2, "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaac");
}).isInstanceOf(CertificationCodeNotMatchedException.class);
}
}

View File

@@ -0,0 +1,3 @@
delete from `posts` where 1;
delete from `users` where 1;

View File

@@ -0,0 +1,5 @@
insert into `users` (`id`, `email`, `nickname`, `address`, `certification_code`, `status`, `last_login_at`)
values (1, 'kok202@naver.com', 'kok202', 'Seoul', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'ACTIVE', 0);
insert into `posts` (`id`, `content`, `created_at`, `modified_at`, `user_id`)
values (1, 'helloworld', 1678530673958, 0, 1);

View File

@@ -0,0 +1,3 @@
insert into `users` (`id`, `email`, `nickname`, `address`, `certification_code`, `status`, `last_login_at`)
values (1, 'kok202@naver.com', 'kok202', 'Seoul', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'ACTIVE', 0);

View File

@@ -0,0 +1,7 @@
insert into `users` (`id`, `email`, `nickname`, `address`, `certification_code`, `status`, `last_login_at`)
values (1, 'kok202@naver.com', 'kok202', 'Seoul', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'ACTIVE', 0);
insert into `users` (`id`, `email`, `nickname`, `address`, `certification_code`, `status`, `last_login_at`)
values (2, 'kok303@naver.com', 'kok303', 'Seoul', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaab', 'PENDING', 0);
insert into `posts` (`id`, `content`, `created_at`, `modified_at`, `user_id`)
values (1, 'helloworld', 1678530673958, 0, 1);

View File

@@ -0,0 +1,5 @@
insert into `users` (`id`, `email`, `nickname`, `address`, `certification_code`, `status`, `last_login_at`)
values (1, 'kok202@naver.com', 'kok202', 'Seoul', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'ACTIVE', 0);
insert into `users` (`id`, `email`, `nickname`, `address`, `certification_code`, `status`, `last_login_at`)
values (2, 'kok303@naver.com', 'kok303', 'Seoul', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaab', 'PENDING', 0);

View File

@@ -0,0 +1,3 @@
insert into `users` (`id`, `email`, `nickname`, `address`, `certification_code`, `status`, `last_login_at`)
values (1, 'kok202@naver.com', 'kok202', 'Seoul', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'ACTIVE', 0);

View File

@@ -0,0 +1,5 @@
insert into `users` (`id`, `email`, `nickname`, `address`, `certification_code`, `status`, `last_login_at`)
values (1, 'kok202@naver.com', 'kok202', 'Seoul', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'ACTIVE', 0);
insert into `users` (`id`, `email`, `nickname`, `address`, `certification_code`, `status`, `last_login_at`)
values (2, 'kok303@naver.com', 'kok303', 'Seoul', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaab', 'PENDING', 0);

View File

@@ -0,0 +1,9 @@
spring.datasource.url=jdbc:h2:mem:testdb;MODE=MySQL;DB_CLOSE_DELAY=-1
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.h2.console.enabled=true
spring.jpa.hibernate.dialect=org.hibernate.dialect.MariaDBDialect
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=create-drop