diff --git a/src/main/java/com/moiming/core/BaseControllerAdvice.java b/src/main/java/com/moiming/core/BaseControllerAdvice.java index 521c68d..07e23e6 100644 --- a/src/main/java/com/moiming/core/BaseControllerAdvice.java +++ b/src/main/java/com/moiming/core/BaseControllerAdvice.java @@ -1,6 +1,9 @@ package com.moiming.core; import com.moiming.core.exception.CustomNoResultException; +import com.moiming.core.exception.DuplicationException; +import java.util.List; +import java.util.stream.Collectors; import jakarta.validation.ConstraintViolationException; import java.util.ArrayList; import org.springframework.http.HttpStatus; @@ -10,9 +13,6 @@ import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; -import java.util.List; -import java.util.stream.Collectors; - @ControllerAdvice public class BaseControllerAdvice { @@ -56,4 +56,11 @@ public ResponseEntity noResultExceptionExceptionHandler(CustomNoResultEx .status(HttpStatus.NOT_FOUND) .body(e.getMessage()); } + + @ExceptionHandler(DuplicationException.class) + public ResponseEntity duplicationExceptionHandler(DuplicationException e) { + return ResponseEntity + .status(HttpStatus.CONFLICT) + .body(e.getMessage()); + } } diff --git a/src/main/java/com/moiming/core/exception/DuplicationException.java b/src/main/java/com/moiming/core/exception/DuplicationException.java new file mode 100644 index 0000000..034f5a6 --- /dev/null +++ b/src/main/java/com/moiming/core/exception/DuplicationException.java @@ -0,0 +1,11 @@ +package com.moiming.core.exception; + +public class DuplicationException extends RuntimeException { + public DuplicationException() { + super("중복된 데이터가 존재합니다."); + } + + public DuplicationException(String message) { + super(message); + } +} diff --git a/src/main/java/com/moiming/user/application/UserService.java b/src/main/java/com/moiming/user/application/UserService.java new file mode 100644 index 0000000..7cbe475 --- /dev/null +++ b/src/main/java/com/moiming/user/application/UserService.java @@ -0,0 +1,49 @@ +package com.moiming.user.application; + +import static com.moiming.user.domain.UserErrorMessage.EXIST_EMAIL; +import static com.moiming.user.domain.UserErrorMessage.EXIST_ID; + +import com.moiming.core.exception.DuplicationException; +import com.moiming.user.domain.Email; +import com.moiming.user.domain.User; +import com.moiming.user.domain.UserRepository; +import com.moiming.user.dto.SignUpRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + + +@Service +@RequiredArgsConstructor +@Slf4j +public class UserService { + + private final UserRepository userRepository; + + /** + * 회원가입을 진행합니다. + */ + public Long signUp(SignUpRequest request) { + log.info("signUp request: {}", request); + + User singUpUser = User.builder().userId(request.userId()) + .password(request.password()) + .email(Email.of(request.email())) + .name(request.name()) + .birthDate(request.birthDate()) + .build(); + + + userRepository.findByUserId(singUpUser.getUserId()).ifPresent(user -> { + throw new DuplicationException(EXIST_ID.getMessage()); + }); + + userRepository.findByEmail(singUpUser.getEmail()).ifPresent(user -> { + throw new DuplicationException(EXIST_EMAIL.getMessage()); + }); + + return userRepository.save(singUpUser) + .getSeq(); + } + +} diff --git a/src/main/java/com/moiming/user/domain/Email.java b/src/main/java/com/moiming/user/domain/Email.java new file mode 100644 index 0000000..eeb1657 --- /dev/null +++ b/src/main/java/com/moiming/user/domain/Email.java @@ -0,0 +1,51 @@ +package com.moiming.user.domain; + +import jakarta.persistence.Embeddable; +import jakarta.persistence.Transient; +import java.util.regex.Pattern; +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; + + +@Embeddable +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@EqualsAndHashCode(of = "value") +public class Email { + private static final String EMAIL_REGEX = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6}$"; + private static final Pattern EMAIL_PATTERN = Pattern.compile(EMAIL_REGEX); + private static final String EMAIL_AT = "@"; + + @Transient + private String id; + + @Transient + private String domain; + + private String value; + + private Email(String email) { + validate(email); + + String[] split = email.split(EMAIL_AT); + this.id = split[0]; + this.domain = split[1]; + this.value = email; + } + + public static Email of(String email) { + return new Email(email); + } + + private void validate(String email) { + if (email == null || email.isEmpty()) { + throw new IllegalArgumentException("이메일 주소가 비어있습니다.."); + } + + if (!EMAIL_PATTERN.matcher(email).find()) { + throw new IllegalArgumentException("잘못된 이메일 형식입니다."); + } + } +} diff --git a/src/main/java/com/moiming/user/domain/User.java b/src/main/java/com/moiming/user/domain/User.java new file mode 100644 index 0000000..135cfd7 --- /dev/null +++ b/src/main/java/com/moiming/user/domain/User.java @@ -0,0 +1,104 @@ +package com.moiming.user.domain; + +import jakarta.persistence.AttributeOverride; +import jakarta.persistence.AttributeOverrides; +import jakarta.persistence.Column; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import java.time.LocalDate; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.Comment; + +@Entity +@Table(name = "users") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "USER_SEQ") + @Comment("유저 KEY") + private Long seq; + + @NotNull + @Size(min = 4, max = 20) + @Column(name = "USER_ID", unique = true) + @Comment("유저 아이디") + private String userId; + + @Embedded + @NotNull + @AttributeOverrides( + @AttributeOverride(name = "value", column = @Column(name = "EMAIL", unique = true, nullable = false, columnDefinition = "VARCHAR(50)")) + ) + @Comment("유저 이메일") + private Email email; + + @NotNull + @Size(max = 20) + @Column(name = "NAME") + @Comment("유저 이름") + private String name; + + @NotNull + @Size(max = 40) + @Column(name = "PASSWORD") + @Comment("패스워드") + private String password; + + @NotNull + @Column(name = "BIRTH_DATE") + @Comment("생일") + private LocalDate birthDate; + + @NotNull + @Size(max = 1) + @Column(name = "USE_YN") + @Comment("사용여부") + private String useYn; + + @NotNull + @Size(max = 1) + @Column(name = "AUTH_YN") + @Comment("이메일 인증 여부") + private String authYn; + + @Builder + private User(Long seq, String userId, Email email, String name, String password, LocalDate birthDate) { + validateNonEmpty(userId, "유저아이디"); + validateNonEmpty(password, "패스워드"); + validateNonEmpty(name, "이름"); + validateNonNull(birthDate, "생년월일"); + + this.seq = seq; + this.userId = userId; + this.email = email; + this.name = name; + this.password = password; + this.birthDate = birthDate; + this.useYn = "Y"; + this.authYn = "N"; + } + + private void validateNonEmpty(String value, String fieldName) { + if (value == null || value.isEmpty()) { + throw new IllegalArgumentException(fieldName + "이(가) 비어있습니다."); + } + } + + private void validateNonNull(T value, String fieldName) { + if (value == null) { + throw new IllegalArgumentException(fieldName + "이(가) 비어있습니다."); + } + } +} diff --git a/src/main/java/com/moiming/user/domain/UserErrorMessage.java b/src/main/java/com/moiming/user/domain/UserErrorMessage.java new file mode 100644 index 0000000..6543eb9 --- /dev/null +++ b/src/main/java/com/moiming/user/domain/UserErrorMessage.java @@ -0,0 +1,15 @@ +package com.moiming.user.domain; + +import lombok.Getter; + +public enum UserErrorMessage { + EXIST_ID("이미 존재하는 아이디입니다."), + EXIST_EMAIL("이미 존재하는 이메일입니다."); + + @Getter + private final String message; + + UserErrorMessage(String message) { + this.message = message; + } +} diff --git a/src/main/java/com/moiming/user/domain/UserRepository.java b/src/main/java/com/moiming/user/domain/UserRepository.java new file mode 100644 index 0000000..c7eaf39 --- /dev/null +++ b/src/main/java/com/moiming/user/domain/UserRepository.java @@ -0,0 +1,12 @@ +package com.moiming.user.domain; + +import java.util.Optional; + +public interface UserRepository { + User save(User user); + User saveAndFlush(User user); + + Optional findByUserId(String id); + + Optional findByEmail(Email email); +} diff --git a/src/main/java/com/moiming/user/dto/SignUpRequest.java b/src/main/java/com/moiming/user/dto/SignUpRequest.java new file mode 100644 index 0000000..d71c50f --- /dev/null +++ b/src/main/java/com/moiming/user/dto/SignUpRequest.java @@ -0,0 +1,38 @@ +package com.moiming.user.dto; + + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import java.time.LocalDate; +import lombok.Builder; + +@Builder +@Schema(description = "회원 가입 요청") +public record SignUpRequest( + @NotNull(message = "아이디는 필수입니다.") + @Size(min = 4, max = 20, message = "아이디는 4자 이상 20자 이하로 입력해주세요.") + @Schema(description = "아이디", example = "moiming") + String userId, + + @NotNull + @Size(min = 4, max = 20, message = "비밀번호는 4자 이상 20자 이하로 입력해주세요.") + @Schema(description = "비밀번호", example = "asddad") + String password, + + @NotNull + @Email(message = "이메일 형식이 올바르지 않습니다.") + @Schema(description = "이메일", example = "sadas@asd.com") + String email, + + @NotNull + @Size(min = 1, max = 20, message = "이름은 1자 이상 20자 이하로 입력해주세요.") + @Schema(description = "이름", example = "이름") + String name, + + @NotNull(message = "생년월일은 필수입니다.") + @Schema(description = "생년월일", example = "2021-08-23") + LocalDate birthDate +) { +} diff --git a/src/main/java/com/moiming/user/infra/JpaUserRepository.java b/src/main/java/com/moiming/user/infra/JpaUserRepository.java new file mode 100644 index 0000000..7c00924 --- /dev/null +++ b/src/main/java/com/moiming/user/infra/JpaUserRepository.java @@ -0,0 +1,9 @@ +package com.moiming.user.infra; + +import com.moiming.user.domain.User; +import com.moiming.user.domain.UserRepository; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface JpaUserRepository extends JpaRepository, UserRepository { + +} diff --git a/src/main/java/com/moiming/user/web/UserController.java b/src/main/java/com/moiming/user/web/UserController.java new file mode 100644 index 0000000..e412737 --- /dev/null +++ b/src/main/java/com/moiming/user/web/UserController.java @@ -0,0 +1,24 @@ +package com.moiming.user.web; + +import com.moiming.user.application.UserService; +import com.moiming.user.dto.SignUpRequest; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@Tag(name = "유저", description = "유저 관련 API") +public class UserController { + + private final UserService userService; + @PostMapping("/sign-up") + @Operation(summary = "회원가입", description = "회원가입을 진행합니다.") + public void signUp(@Valid @RequestBody SignUpRequest request) { + userService.signUp(request); + } +} diff --git a/src/test/java/com/moiming/config/AcceptanceTestExecutionListener.java b/src/test/java/com/moiming/config/AcceptanceTestExecutionListener.java new file mode 100644 index 0000000..571bad5 --- /dev/null +++ b/src/test/java/com/moiming/config/AcceptanceTestExecutionListener.java @@ -0,0 +1,38 @@ +package com.moiming.config; + +import java.util.List; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.test.context.TestContext; +import org.springframework.test.context.support.AbstractTestExecutionListener; + + +public class AcceptanceTestExecutionListener extends AbstractTestExecutionListener { + + @Override + public void afterTestMethod(final TestContext testContext) { + final JdbcTemplate jdbcTemplate = getJdbcTemplate(testContext); + final List truncateQueries = getTruncateQueries(jdbcTemplate); + truncateTables(jdbcTemplate, truncateQueries); + } + + private List getTruncateQueries(final JdbcTemplate jdbcTemplate) { + return jdbcTemplate.queryForList( + "SELECT Concat('TRUNCATE TABLE ', TABLE_NAME, ';') AS q FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'PUBLIC'", + String.class); + } + + private JdbcTemplate getJdbcTemplate(final TestContext testContext) { + return testContext.getApplicationContext().getBean(JdbcTemplate.class); + } + + private void truncateTables(final JdbcTemplate jdbcTemplate, final List truncateQueries) { + execute(jdbcTemplate, "SET REFERENTIAL_INTEGRITY FALSE"); + truncateQueries.forEach(v -> execute(jdbcTemplate, v)); + execute(jdbcTemplate, "SET REFERENTIAL_INTEGRITY TRUE"); + } + + private void execute(final JdbcTemplate jdbcTemplate, final String query) { + jdbcTemplate.execute(query); + } + +} diff --git a/src/test/java/com/moiming/config/IntegrationTest.java b/src/test/java/com/moiming/config/IntegrationTest.java new file mode 100644 index 0000000..ce863d6 --- /dev/null +++ b/src/test/java/com/moiming/config/IntegrationTest.java @@ -0,0 +1,16 @@ +package com.moiming.config; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.test.context.TestExecutionListeners; + + +@SpringBootTest(webEnvironment = WebEnvironment.MOCK) +@AutoConfigureMockMvc +@TestExecutionListeners(value = {AcceptanceTestExecutionListener.class,}, mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS) +@Retention(RetentionPolicy.RUNTIME) +public @interface IntegrationTest { +} diff --git a/src/test/java/com/moiming/user/application/UserServiceTest.java b/src/test/java/com/moiming/user/application/UserServiceTest.java new file mode 100644 index 0000000..5c6705d --- /dev/null +++ b/src/test/java/com/moiming/user/application/UserServiceTest.java @@ -0,0 +1,102 @@ +package com.moiming.user.application; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.moiming.core.exception.DuplicationException; +import com.moiming.user.domain.Email; +import com.moiming.user.domain.User; +import com.moiming.user.domain.UserRepository; +import com.moiming.user.dto.SignUpRequest; +import com.moiming.user.stub.UserRepositoryStub; +import java.time.LocalDate; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class UserServiceTest { + + private UserRepository userRepository; + private UserService userService; + + @BeforeEach + void setUp() { + userRepository = new UserRepositoryStub(); + userService = new UserService(userRepository); + } + + @Test + @DisplayName("회원가입 성공") + void signUp() { + //given + SignUpRequest request = SignUpRequest.builder() + .userId("1") + .name("name") + .password("password") + .email("asdsadsad02@gmial.com") + .birthDate(LocalDate.of(1995, 1, 1)) + .build(); + + //when + final Long seq = userService.signUp(request); + + //then + assertThat(seq).isEqualTo(1L); + } + + @Test + @DisplayName("회원가입 실패 - 아이디 중복") + void signUpFail() { + //given + final User 저장된_유저 = 저장된_유저(User.builder() + .userId("1") + .name("name") + .password("password") + .birthDate(LocalDate.of(1995, 1, 1)) + .email(Email.of("kbh05@sadasd.com")) + .build()); + + SignUpRequest request = SignUpRequest.builder() + .userId(저장된_유저.getUserId()) + .name("name") + .password("password") + .email("kbh05@sadasd.com") + .birthDate(LocalDate.of(1995, 1, 1)) + .build(); + + //when & then + assertThatThrownBy(() -> userService.signUp(request)) + .isInstanceOf(DuplicationException.class); + } + + @Test + @DisplayName("회원가입 실패 - 이메일 중복") + void signUpFail2() { + //given + final User 저장된_유저 = 저장된_유저(User.builder() + .userId("1") + .name("name") + .password("password") + .birthDate(LocalDate.of(1995, 1, 1)) + .email(Email.of("kbh232@gmail.com")) + .build()); + + SignUpRequest request = SignUpRequest.builder() + .userId("2") + .name("name") + .password("password") + .email(저장된_유저.getEmail().getValue()) + .birthDate(LocalDate.of(1995, 1, 1)) + .build(); + + //when & then + assertThatThrownBy(() -> userService.signUp(request)) + .isInstanceOf(DuplicationException.class); + + } + + private User 저장된_유저(User user) { + return userRepository.save(user); + } + +} \ No newline at end of file diff --git a/src/test/java/com/moiming/user/domain/EmailTest.java b/src/test/java/com/moiming/user/domain/EmailTest.java new file mode 100644 index 0000000..98c22d7 --- /dev/null +++ b/src/test/java/com/moiming/user/domain/EmailTest.java @@ -0,0 +1,62 @@ +package com.moiming.user.domain; + + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.NullAndEmptySource; + +class EmailTest { + + @ParameterizedTest + @NullAndEmptySource + @DisplayName("이메일 주소가 비어있으면 안된다.") + void notEmpty(String email) { + assertThatIllegalArgumentException() + .isThrownBy(() -> Email.of(email)); + } + + @ParameterizedTest + @CsvSource(value = {"abc", "abc@abc", "abc@abc."}) + @DisplayName("이메일 형식이 잘못되면 안된다.") + void validEmail(String email) { + assertThatIllegalArgumentException() + .isThrownBy(() -> Email.of(email)); + } + + @Test + @DisplayName("이메일이 생성이 된다.") + void createEmail() { + // given + String emailString = "kbh052@gmail.com"; + + //when + Email email = Email.of(emailString); + + //then + assertSoftly(softly -> { + softly.assertThat(email.getId()).isEqualTo("kbh052"); + softly.assertThat(email.getDomain()).isEqualTo("gmail.com"); + softly.assertThat(email.getValue()).isEqualTo(emailString); + }); + } + + @Test + @DisplayName("이메일이 같으면 같은 객체이다.") + void equalsEmail() { + // given + String emailString = "sadsa@gmail.com"; + + // when + Email email1 = Email.of(emailString); + Email email2 = Email.of(emailString); + + // then + assertThat(email1).isEqualTo(email2); + } +} diff --git a/src/test/java/com/moiming/user/domain/UserTest.java b/src/test/java/com/moiming/user/domain/UserTest.java new file mode 100644 index 0000000..791f939 --- /dev/null +++ b/src/test/java/com/moiming/user/domain/UserTest.java @@ -0,0 +1,73 @@ +package com.moiming.user.domain; + +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +import java.time.LocalDate; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; + + +class UserTest { + + private Email collectEmail; + + @BeforeEach + void setUp() { + collectEmail = Email.of("kbh058@naver.com"); + } + + @ParameterizedTest + @NullAndEmptySource + @DisplayName("유저의 이름은 비어 있으면 안된다") + void noNameUser(String name) { + assertThatIllegalArgumentException().isThrownBy( + () -> User.builder().birthDate(LocalDate.of(1995, 8, 2)) + .email(collectEmail) + .userId("kbh052") + .name(name) + .password("1234") + .build()); + + } + + @ParameterizedTest + @NullAndEmptySource + @DisplayName("유저의 아이디는 비어 있으면 안된다") + void noIdUser(String userId) { + assertThatIllegalArgumentException().isThrownBy( + () -> User.builder().birthDate(LocalDate.of(1995, 8, 2)) + .email(collectEmail) + .userId(userId) + .name("name") + .password("1234") + .build()); + } + + @ParameterizedTest + @NullAndEmptySource + @DisplayName("유저의 비밀번호는 비어 있으면 안된다") + void noPasswordUser(String password) { + assertThatIllegalArgumentException().isThrownBy(() + -> User.builder().birthDate(LocalDate.of(1995, 8, 2)) + .email(collectEmail) + .userId("kbh052") + .name("name") + .password(password) + .build()); + } + + @Test + @DisplayName("유저의 생일은 비어 있으면 안된다") + void noBirthDateUser() { + assertThatIllegalArgumentException().isThrownBy(() + -> User.builder().birthDate(null) + .email(collectEmail) + .userId("kbh052") + .name("name") + .password("1234") + .build()); + } +} diff --git a/src/test/java/com/moiming/user/stub/UserRepositoryStub.java b/src/test/java/com/moiming/user/stub/UserRepositoryStub.java new file mode 100644 index 0000000..2de270e --- /dev/null +++ b/src/test/java/com/moiming/user/stub/UserRepositoryStub.java @@ -0,0 +1,53 @@ +package com.moiming.user.stub; + +import com.moiming.user.domain.Email; +import com.moiming.user.domain.User; +import com.moiming.user.domain.UserRepository; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +public class UserRepositoryStub implements UserRepository { + + private Map users = new ConcurrentHashMap<>(); + + @Override + public User save(User user) { + Long key = users.keySet().stream().max(Long::compareTo).orElse(0L) + 1L; + + User newUser = User.builder() + .seq(key) + .userId(user.getUserId()) + .birthDate(user.getBirthDate()) + .email(user.getEmail()) + .name(user.getName()) + .password(user.getPassword()) + .build(); + + users.put(key, newUser); + + return users.get(key); + } + + @Override + public User saveAndFlush(User user) { + return save(user); + } + + @Override + public Optional findByUserId(String userId) { + return users.values() + .stream() + .filter(it -> it.getUserId().equals(userId)) + .findFirst(); + + } + + @Override + public Optional findByEmail(Email email) { + return users.values() + .stream() + .filter(it -> it.getEmail().equals(email)) + .findFirst(); + } +} diff --git a/src/test/java/com/moiming/user/web/UserControllerTest.java b/src/test/java/com/moiming/user/web/UserControllerTest.java new file mode 100644 index 0000000..1f582e4 --- /dev/null +++ b/src/test/java/com/moiming/user/web/UserControllerTest.java @@ -0,0 +1,233 @@ +package com.moiming.user.web; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.log; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.moiming.config.IntegrationTest; +import com.moiming.user.dto.SignUpRequest; +import java.time.LocalDate; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; + +@IntegrationTest +class UserControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Test + @DisplayName("회원가입 성공") + void signUp() throws Exception { + //given + SignUpRequest signUpRequest = SignUpRequest.builder() + .userId("kbh2") + .password("패스워드") + .email("kbh0581@Gmail.com") + .name("이름") + .birthDate(LocalDate.now()) + .build(); + + //when + final ResultActions 회원가입_요청 = 회원가입_요청(signUpRequest); + + //then + 회원가입_요청.andDo(print()) + .andExpect(status().isOk()); + } + + @Test + @DisplayName("회원가입 실패 - 아이디 중복") + void signUpFail() throws Exception { + //given + SignUpRequest 회원1 = SignUpRequest.builder() + .userId("kbh2") + .password("패스워드") + .email("kbh0581@Gmail.com") + .name("이름") + .birthDate(LocalDate.now()) + .build(); + + //given + 이미_회원_가입이_되어진_유저(회원1); + + SignUpRequest 중복된_이메일_회원가입_요청 = SignUpRequest.builder() + .userId(회원1.userId()) + .password("패스워드") + .email("kbh0581@Gmail.com") + .name("이름") + .birthDate(LocalDate.now()) + .build(); + + //when + final ResultActions 회원가입_요청 = 회원가입_요청(중복된_이메일_회원가입_요청); + + //then + 회원가입_요청.andDo(print()) + .andExpect(status().isConflict()); + } + + @Test + @DisplayName("회원가입 실패 - 이메일 중복") + void signUpFail2() throws Exception { + //given + SignUpRequest 회원1 = SignUpRequest.builder() + .userId("kbh2") + .password("패스워드") + .email("kbh0581@Gmail.com") + .name("이름") + .birthDate(LocalDate.now()) + .build(); + + //given + 이미_회원_가입이_되어진_유저(회원1); + + SignUpRequest 중복된_이메일_회원가입_요청 = SignUpRequest.builder() + .userId("kbh3") + .password("패스워드") + .email(회원1.email()) + .name("이름") + .birthDate(LocalDate.now()) + .build(); + + //when + final ResultActions 회원가입_요청 = 회원가입_요청(중복된_이메일_회원가입_요청); + + //then + 회원가입_요청.andDo(print()) + .andExpect(status().isConflict()); + } + + + @ParameterizedTest + @ValueSource(strings = {"123", "123456789012345678901"}) + @NullAndEmptySource + @DisplayName("회원가입 실패 - 비밀번호가 4자 ~ 20자 이상") + void signUpFail3(String password) throws Exception { + //given & when + 회원가입_요청(SignUpRequest.builder() + .userId("kbh2") + .password(password) + .email("kbh0581@Gmail.com") + .name("이름") + .birthDate(LocalDate.now()) + .build()) + + //then + .andExpect(status().isBadRequest()); + } + + @ParameterizedTest + @ValueSource(strings = {"123", "123456789012345678901"}) + @NullAndEmptySource + @DisplayName("회원가입 실패 - 아이디가 비밀번호가 4자 ~ 20자 이상") + void signUpFail4(String userId) throws Exception { + //given & when + 회원가입_요청(SignUpRequest.builder() + .userId(userId) + .password("패스워드") + .email("kbh0581@Gmail.com") + .name("이름") + .birthDate(LocalDate.now()) + .build()) + //then + .andExpect(status().isBadRequest()); + } + + @ParameterizedTest + @ValueSource(strings = {"123", "123456789012345678901"}) + @NullAndEmptySource + @DisplayName("회원가입 실패 - 아이디가 4자 ~ 20자 이상") + void signUpFail5(String userId) throws Exception { + //given + 회원가입_요청(SignUpRequest.builder() + .userId(userId) + .password("패스워드") + .email("kbh0581@Gmail.com") + .name("이름") + .birthDate(LocalDate.now()) + .build()) + //then + .andExpect(status().isBadRequest()); + } + + @ParameterizedTest + @NullAndEmptySource + @DisplayName("회원가입 실패 - 이름이 비어있음") + void signUpFail6(String name) throws Exception { + 회원가입_요청(SignUpRequest.builder() + .userId("kbh2") + .password("패스워드") + .email("kbh0581@Gmail.com") + .name(name) + .birthDate(LocalDate.now()) + .build()) + //then + .andExpect(status().isBadRequest()); + ; + } + + @Test + @DisplayName("회원가입 실패 - birthDate가 비어있음") + void signUpFail7() throws Exception { + 회원가입_요청(SignUpRequest.builder() + .userId("kbh2") + .password("패스워드") + .email("kbh0581@Gmail.com") + .name("name") + .birthDate(null) + .build()) + //then + .andExpect(status().isBadRequest()); + ; + } + + + @ParameterizedTest + @ValueSource(strings = {"123", "1234567@89012345678901", "@kbg.com"}) + @NullAndEmptySource + @DisplayName("회원가입 실패 - 유효하지 않은 이메일") + void signUpFail8(String email) throws Exception { + 회원가입_요청(SignUpRequest.builder() + .userId("kbh2") + .password("패스워드") + .email(email) + .name("name") + .birthDate(null) + .build()) + //then + .andExpect(status().isBadRequest()); + ; + } + + private ResultActions 회원가입_요청(SignUpRequest signUpRequest) throws Exception { + return mockMvc.perform(post("/sign-up") + .contentType(MediaType.APPLICATION_JSON) + .content(json변환(signUpRequest))) + .andDo(log()) + .andDo(print()); + } + + private void 이미_회원_가입이_되어진_유저(SignUpRequest signUpRequest) throws Exception { + 회원가입_요청(signUpRequest) + .andExpect(status().isOk()); + } + + private String json변환(Object payload) throws JsonProcessingException { + return objectMapper.writeValueAsString(payload); + } + +}