From 437488f100ef75fef277d60da095fec83331a8ab Mon Sep 17 00:00:00 2001 From: ekgns33 <76658405+ekgns33@users.noreply.github.com> Date: Sat, 20 Jul 2024 22:22:05 +0900 Subject: [PATCH] Bp 25 implement user registration (#6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat : 회원가입 api 권한 수정, 엔드포인트 prefix 메소드 추가 * feat : 에러코드, 커스텀 에러 추가, 전역 핸들러에 로직구현 * feat : 회원가입 DTO추가, 멤버 컨트롤러 추가 * feat : 멤버 서비스 추가, 회원가입 기능 추가 * test : 컨트롤러 테스트, 유닛테스트 작성 * feat : 유저 비밀번호 암호화 추가 * rename : 파일 위치 수정 * test : 암호화 로직 추가 * rename : DTO 컨트롤러 패키지로 이동 * refactor : 매직넘버 삭제, HttpStatus사용 * refactor : 기존 ErrorCode를 인터페이스화, 도메인마다 에러코드 정의 * refactor : 에러코드 구조 변경에 따른 코드 수정 * chore : spring-validation 추가 --- build.gradle | 1 + .../CustomAuthenticationFailureHandler.java | 7 +- .../auth/CustomUserDetailsService.java | 8 +- .../exceptions/InvalidUserInfoException.java | 17 --- .../application/member/MemberService.java | 43 ++++++ .../member/exceptions/MemberErrorCode.java | 37 +++++ .../exceptions/UserAlreadyExistException.java | 15 ++ .../controller/member/MemberController.java | 27 ++++ .../member/MemberRegisterRequest.java | 32 +++++ .../global/configs/SecurityConfig.java | 15 +- .../controller/GlobalExceptionHandler.java | 9 +- .../global/exceptions/BusinessException.java | 9 +- .../global/exceptions/CustomErrorCode.java | 10 ++ .../global/exceptions/ErrorCode.java | 23 --- .../global/exceptions/GlobalErrorCode.java | 37 +++++ .../global/responses/ErrorResponse.java | 8 +- .../application/member/MemberServiceTest.java | 82 +++++++++++ .../member/MemberControllerTest.java | 133 ++++++++++++++++++ 18 files changed, 443 insertions(+), 70 deletions(-) delete mode 100644 src/main/java/gdsc/konkuk/platformcore/application/auth/exceptions/InvalidUserInfoException.java create mode 100644 src/main/java/gdsc/konkuk/platformcore/application/member/MemberService.java create mode 100644 src/main/java/gdsc/konkuk/platformcore/application/member/exceptions/MemberErrorCode.java create mode 100644 src/main/java/gdsc/konkuk/platformcore/application/member/exceptions/UserAlreadyExistException.java create mode 100644 src/main/java/gdsc/konkuk/platformcore/controller/member/MemberController.java create mode 100644 src/main/java/gdsc/konkuk/platformcore/controller/member/MemberRegisterRequest.java create mode 100644 src/main/java/gdsc/konkuk/platformcore/global/exceptions/CustomErrorCode.java delete mode 100644 src/main/java/gdsc/konkuk/platformcore/global/exceptions/ErrorCode.java create mode 100644 src/main/java/gdsc/konkuk/platformcore/global/exceptions/GlobalErrorCode.java create mode 100644 src/test/java/gdsc/konkuk/platformcore/application/member/MemberServiceTest.java create mode 100644 src/test/java/gdsc/konkuk/platformcore/controller/member/MemberControllerTest.java diff --git a/build.gradle b/build.gradle index 94fa59f..efb52d4 100644 --- a/build.gradle +++ b/build.gradle @@ -41,6 +41,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-security:2.3.3.RELEASE' + implementation 'org.springframework.boot:spring-boot-starter-validation' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' annotationProcessor 'org.projectlombok:lombok' diff --git a/src/main/java/gdsc/konkuk/platformcore/application/auth/CustomAuthenticationFailureHandler.java b/src/main/java/gdsc/konkuk/platformcore/application/auth/CustomAuthenticationFailureHandler.java index 02e3a39..3ad4994 100644 --- a/src/main/java/gdsc/konkuk/platformcore/application/auth/CustomAuthenticationFailureHandler.java +++ b/src/main/java/gdsc/konkuk/platformcore/application/auth/CustomAuthenticationFailureHandler.java @@ -8,9 +8,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; -import gdsc.konkuk.platformcore.global.exceptions.ErrorCode; +import gdsc.konkuk.platformcore.application.member.exceptions.MemberErrorCode; import gdsc.konkuk.platformcore.global.responses.ErrorResponse; -import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; @@ -23,9 +22,9 @@ public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationF @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws - IOException, ServletException { + IOException { - ErrorResponse errorResponse = ErrorResponse.of(ErrorCode.INVALID_USER_INFO); + ErrorResponse errorResponse = ErrorResponse.of(MemberErrorCode.INVALID_USER_INFO); response.setContentType("application/json"); response.setCharacterEncoding("UTF-8"); response.setStatus(HttpServletResponse.SC_BAD_REQUEST); diff --git a/src/main/java/gdsc/konkuk/platformcore/application/auth/CustomUserDetailsService.java b/src/main/java/gdsc/konkuk/platformcore/application/auth/CustomUserDetailsService.java index 901cbc5..5d49c9b 100644 --- a/src/main/java/gdsc/konkuk/platformcore/application/auth/CustomUserDetailsService.java +++ b/src/main/java/gdsc/konkuk/platformcore/application/auth/CustomUserDetailsService.java @@ -5,10 +5,10 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; -import gdsc.konkuk.platformcore.application.auth.exceptions.InvalidUserInfoException; +import gdsc.konkuk.platformcore.application.member.exceptions.MemberErrorCode; import gdsc.konkuk.platformcore.domain.member.entity.Member; import gdsc.konkuk.platformcore.domain.member.repository.MemberRepository; -import gdsc.konkuk.platformcore.global.exceptions.ErrorCode; +import gdsc.konkuk.platformcore.global.exceptions.BusinessException; import lombok.RequiredArgsConstructor; @Service @@ -21,8 +21,8 @@ public class CustomUserDetailsService implements UserDetailsService { public UserDetails loadUserByUsername(String memberId) throws UsernameNotFoundException { Member member = memberRepository.findByMemberId(memberId) - .orElseThrow(()-> InvalidUserInfoException.of(ErrorCode.USER_NOT_FOUND)); - + .orElseThrow(()-> BusinessException.of(MemberErrorCode.USER_NOT_FOUND)); + return new CustomUserDetails(member); } } diff --git a/src/main/java/gdsc/konkuk/platformcore/application/auth/exceptions/InvalidUserInfoException.java b/src/main/java/gdsc/konkuk/platformcore/application/auth/exceptions/InvalidUserInfoException.java deleted file mode 100644 index ecee3f1..0000000 --- a/src/main/java/gdsc/konkuk/platformcore/application/auth/exceptions/InvalidUserInfoException.java +++ /dev/null @@ -1,17 +0,0 @@ -package gdsc.konkuk.platformcore.application.auth.exceptions; - -import gdsc.konkuk.platformcore.global.exceptions.BusinessException; -import gdsc.konkuk.platformcore.global.exceptions.ErrorCode; - -public class InvalidUserInfoException extends BusinessException { - - private InvalidUserInfoException(String message, String logMessage) { - super(message, logMessage); - } - - public static InvalidUserInfoException of(ErrorCode errorCode) { - return new InvalidUserInfoException(errorCode.getMessage(), errorCode.getLogMessage()); - } - -} - diff --git a/src/main/java/gdsc/konkuk/platformcore/application/member/MemberService.java b/src/main/java/gdsc/konkuk/platformcore/application/member/MemberService.java new file mode 100644 index 0000000..d961ae7 --- /dev/null +++ b/src/main/java/gdsc/konkuk/platformcore/application/member/MemberService.java @@ -0,0 +1,43 @@ +package gdsc.konkuk.platformcore.application.member; + +import java.util.Optional; + +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import gdsc.konkuk.platformcore.application.member.exceptions.MemberErrorCode; +import gdsc.konkuk.platformcore.application.member.exceptions.UserAlreadyExistException; +import gdsc.konkuk.platformcore.controller.member.MemberRegisterRequest; +import gdsc.konkuk.platformcore.domain.member.entity.Member; +import gdsc.konkuk.platformcore.domain.member.repository.MemberRepository; +import gdsc.konkuk.platformcore.global.responses.SuccessResponse; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class MemberService { + + private final PasswordEncoder passwordEncoder; + private final MemberRepository memberRepository; + + @Transactional + public SuccessResponse register(MemberRegisterRequest registerRequest) { + + if(checkMemberAlreadyExist(registerRequest.getMemberId())) { + throw UserAlreadyExistException.of(MemberErrorCode.USER_ALREADY_EXISTS); + } + + String encodedPassword = passwordEncoder.encode(registerRequest.getPassword()); + registerRequest.setPassword(encodedPassword); + memberRepository.save(MemberRegisterRequest.toEntity(registerRequest)); + + return SuccessResponse.messageOnly(); + } + + private boolean checkMemberAlreadyExist(String memberId) { + Optional member = memberRepository.findByMemberId(memberId); + return member.isPresent(); + } +} diff --git a/src/main/java/gdsc/konkuk/platformcore/application/member/exceptions/MemberErrorCode.java b/src/main/java/gdsc/konkuk/platformcore/application/member/exceptions/MemberErrorCode.java new file mode 100644 index 0000000..417570c --- /dev/null +++ b/src/main/java/gdsc/konkuk/platformcore/application/member/exceptions/MemberErrorCode.java @@ -0,0 +1,37 @@ +package gdsc.konkuk.platformcore.application.member.exceptions; + +import gdsc.konkuk.platformcore.global.exceptions.CustomErrorCode; +import lombok.Getter; + +@Getter +public enum MemberErrorCode implements CustomErrorCode { + + USER_NOT_FOUND("사용자가 존재하지 않습니다. 다시 입력해주세요", "[ERROR] : 사용자 정보를 찾을 수 없음"), + INVALID_USER_INFO("사용자 정보가 올바르지 않습니다. 다시 입력해주세요", "[ERROR] : 사용자 정보가 올바르지 않음"), + DEACTIVATED_USER("비활성화된 사용자입니다. 다시 확인해주세요", "[ERROR] : 탈퇴한 사용자"), + USER_ALREADY_EXISTS("이미 존재하는 사용자입니다.", "[ERROR] : 이미 존재하는 사용자"); + + private final String message; + private final String logMessage; + + MemberErrorCode(String message, String logMessage) { + this.message = message; + this.logMessage = logMessage; + } + + + @Override + public String getLogMessage() { + return this.logMessage; + } + + @Override + public String getName() { + return this.name(); + } + + @Override + public String getMessage() { + return this.message; + } +} diff --git a/src/main/java/gdsc/konkuk/platformcore/application/member/exceptions/UserAlreadyExistException.java b/src/main/java/gdsc/konkuk/platformcore/application/member/exceptions/UserAlreadyExistException.java new file mode 100644 index 0000000..899c3af --- /dev/null +++ b/src/main/java/gdsc/konkuk/platformcore/application/member/exceptions/UserAlreadyExistException.java @@ -0,0 +1,15 @@ +package gdsc.konkuk.platformcore.application.member.exceptions; + +import gdsc.konkuk.platformcore.global.exceptions.BusinessException; +import gdsc.konkuk.platformcore.global.exceptions.CustomErrorCode; + +public class UserAlreadyExistException extends BusinessException { + + protected UserAlreadyExistException(CustomErrorCode errorCode, String logMessage) { + super(errorCode, logMessage); + } + + public static UserAlreadyExistException of(CustomErrorCode errorCode) { + return new UserAlreadyExistException(errorCode, errorCode.getLogMessage()); + } +} diff --git a/src/main/java/gdsc/konkuk/platformcore/controller/member/MemberController.java b/src/main/java/gdsc/konkuk/platformcore/controller/member/MemberController.java new file mode 100644 index 0000000..256e65a --- /dev/null +++ b/src/main/java/gdsc/konkuk/platformcore/controller/member/MemberController.java @@ -0,0 +1,27 @@ +package gdsc.konkuk.platformcore.controller.member; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import gdsc.konkuk.platformcore.application.member.MemberService; +import gdsc.konkuk.platformcore.global.responses.SuccessResponse; +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping("/api/v1/members") +@RequiredArgsConstructor +public class MemberController { + + private final MemberService memberService; + + @PostMapping() + public ResponseEntity signup(@RequestBody MemberRegisterRequest registerRequest) { + memberService.register(registerRequest); + return ResponseEntity.status(HttpStatus.CREATED).body(SuccessResponse.messageOnly()); + } + +} diff --git a/src/main/java/gdsc/konkuk/platformcore/controller/member/MemberRegisterRequest.java b/src/main/java/gdsc/konkuk/platformcore/controller/member/MemberRegisterRequest.java new file mode 100644 index 0000000..2d15f14 --- /dev/null +++ b/src/main/java/gdsc/konkuk/platformcore/controller/member/MemberRegisterRequest.java @@ -0,0 +1,32 @@ +package gdsc.konkuk.platformcore.controller.member; + +import gdsc.konkuk.platformcore.domain.member.entity.Member; +import jakarta.validation.constraints.NotEmpty; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Builder +public class MemberRegisterRequest { + @NotEmpty + private String memberId; + @NotEmpty + private String password; + @NotEmpty + private String name; + @NotEmpty + private String email; + private int batch; + + public static Member toEntity(MemberRegisterRequest request) { + return Member.builder() + .memberId(request.getMemberId()) + .password(request.getPassword()) + .name(request.getName()) + .email(request.getEmail()) + .batch(request.getBatch()) + .build(); + } +} diff --git a/src/main/java/gdsc/konkuk/platformcore/global/configs/SecurityConfig.java b/src/main/java/gdsc/konkuk/platformcore/global/configs/SecurityConfig.java index e8ef71d..b93c9d7 100644 --- a/src/main/java/gdsc/konkuk/platformcore/global/configs/SecurityConfig.java +++ b/src/main/java/gdsc/konkuk/platformcore/global/configs/SecurityConfig.java @@ -2,10 +2,9 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; -import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; @@ -22,16 +21,16 @@ public class SecurityConfig { private final CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler; private final CustomAuthenticationFailureHandler customAuthenticationFailureHandler; - + private static final String API_PREFIX = "/api/v1"; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception { httpSecurity .addFilterBefore(new SecurityContextPersistenceFilter(), BasicAuthenticationFilter.class) .authorizeHttpRequests(authorize -> authorize - .requestMatchers("/docs/**").permitAll() - .requestMatchers("/admin").hasRole("ADMIN") - .requestMatchers("/member").hasRole("MEMBER") + .requestMatchers(apiPath("/docs/**")).permitAll() + .requestMatchers(HttpMethod.POST, apiPath("/members")).permitAll() + .requestMatchers(apiPath("/admin/**")).hasRole("ADMIN") .anyRequest().authenticated()) .formLogin(login -> login @@ -56,4 +55,8 @@ public SecurityFilterChain swaggerFilterchain(HttpSecurity httpSecurity) throws public BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } + + private String apiPath(String path) { + return API_PREFIX + path; + } } diff --git a/src/main/java/gdsc/konkuk/platformcore/global/controller/GlobalExceptionHandler.java b/src/main/java/gdsc/konkuk/platformcore/global/controller/GlobalExceptionHandler.java index aaebbca..e08ffde 100644 --- a/src/main/java/gdsc/konkuk/platformcore/global/controller/GlobalExceptionHandler.java +++ b/src/main/java/gdsc/konkuk/platformcore/global/controller/GlobalExceptionHandler.java @@ -6,7 +6,7 @@ import org.springframework.web.bind.annotation.RestControllerAdvice; import gdsc.konkuk.platformcore.global.exceptions.BusinessException; -import gdsc.konkuk.platformcore.global.exceptions.ErrorCode; +import gdsc.konkuk.platformcore.global.exceptions.GlobalErrorCode; import gdsc.konkuk.platformcore.global.responses.ErrorResponse; import lombok.extern.slf4j.Slf4j; @@ -14,18 +14,17 @@ @RestControllerAdvice public class GlobalExceptionHandler { - @ExceptionHandler(BusinessException.class) protected ResponseEntity handleBusinessException(final BusinessException e) { - log.error("BusinessException Caught! [{}]", e.getLogMessage()); + log.error("BusinessException Caught! [{}]", e.getLogMessage(), e); final ErrorResponse response = ErrorResponse.of(e.getMessage(), e.getLogMessage()); return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST); } @ExceptionHandler(Exception.class) protected ResponseEntity handleException(Exception e) { - log.error("Exception Uncaught! [{}]", e.getCause().toString()); - final ErrorResponse response = ErrorResponse.of(ErrorCode.INTERNAL_SERVER_ERROR); + log.error("Exception Uncaught!", e); + final ErrorResponse response = ErrorResponse.of(GlobalErrorCode.INTERNAL_SERVER_ERROR); return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR); } } diff --git a/src/main/java/gdsc/konkuk/platformcore/global/exceptions/BusinessException.java b/src/main/java/gdsc/konkuk/platformcore/global/exceptions/BusinessException.java index 4fa4daf..c38563c 100644 --- a/src/main/java/gdsc/konkuk/platformcore/global/exceptions/BusinessException.java +++ b/src/main/java/gdsc/konkuk/platformcore/global/exceptions/BusinessException.java @@ -7,17 +7,12 @@ public class BusinessException extends RuntimeException{ private final String logMessage; - protected BusinessException(String message, String logMessage) { - super(message); - this.logMessage = logMessage; - } - - protected BusinessException(ErrorCode errorCode, String logMessage) { + protected BusinessException(CustomErrorCode errorCode, String logMessage) { super(errorCode.getMessage()); this.logMessage = logMessage; } - public static BusinessException of(ErrorCode errorCode) { + public static BusinessException of(CustomErrorCode errorCode) { return new BusinessException(errorCode, errorCode.getLogMessage()); } diff --git a/src/main/java/gdsc/konkuk/platformcore/global/exceptions/CustomErrorCode.java b/src/main/java/gdsc/konkuk/platformcore/global/exceptions/CustomErrorCode.java new file mode 100644 index 0000000..ba4bd4d --- /dev/null +++ b/src/main/java/gdsc/konkuk/platformcore/global/exceptions/CustomErrorCode.java @@ -0,0 +1,10 @@ +package gdsc.konkuk.platformcore.global.exceptions; + +public interface CustomErrorCode { + + String getLogMessage(); + + String getName(); + + String getMessage(); +} diff --git a/src/main/java/gdsc/konkuk/platformcore/global/exceptions/ErrorCode.java b/src/main/java/gdsc/konkuk/platformcore/global/exceptions/ErrorCode.java deleted file mode 100644 index 67018f8..0000000 --- a/src/main/java/gdsc/konkuk/platformcore/global/exceptions/ErrorCode.java +++ /dev/null @@ -1,23 +0,0 @@ -package gdsc.konkuk.platformcore.global.exceptions; - -import lombok.Getter; -import lombok.ToString; - -@Getter -@ToString -public enum ErrorCode { - - INTERNAL_SERVER_ERROR("서버 오류입니다. 잠시후 재시도 해주세요", "[ERROR] : 예상치못한 에러 발생", 500), - USER_NOT_FOUND("사용자가 존재하지 않습니다. 다시 입력해주세요", "[ERROR] : 사용자 정보를 찾을 수 없음", 404), - INVALID_USER_INFO("사용자 정보가 올바르지 않습니다. 다시 입력해주세요", "[ERROR] : 사용자 정보가 올바르지 않음", 400), - DEACTIVATED_USER("탈퇴한 사용자입니다. 다시 확인해주세요", "[ERROR] : 탈퇴한 사용자", 400); - private final String message; - private final String logMessage; - private final int statusCode; - - ErrorCode(String message, String logMessage, int statusCode) { - this.message = message; - this.logMessage = logMessage; - this.statusCode = statusCode; - } -} diff --git a/src/main/java/gdsc/konkuk/platformcore/global/exceptions/GlobalErrorCode.java b/src/main/java/gdsc/konkuk/platformcore/global/exceptions/GlobalErrorCode.java new file mode 100644 index 0000000..e1c08be --- /dev/null +++ b/src/main/java/gdsc/konkuk/platformcore/global/exceptions/GlobalErrorCode.java @@ -0,0 +1,37 @@ +package gdsc.konkuk.platformcore.global.exceptions; + +import lombok.Getter; + +@Getter +public enum GlobalErrorCode implements CustomErrorCode{ + + // DTO Validation에서 발생한 에러 처리를 위한 코드 + ARGUMENT_NOT_VALID("잘못된 입력입니다. 다시 확인해주세요", "[ERROR] : 400 컨트롤러 벨리데이션 실패 잘못된 인자"), + + NOT_FOUND("찾을 수 없습니다. 다시 확인해주세요", "[ERROR] : 404 에러 발생"), + INTERNAL_SERVER_ERROR("서버 오류입니다. 잠시후 재시도 해주세요", "[ERROR] : 500 예상치못한 에러 발생"); + + private final String message; + private final String logMessage; + + + GlobalErrorCode(String message, String logMessage) { + this.message = message; + this.logMessage = logMessage; + } + + @Override + public String getLogMessage() { + return this.logMessage; + } + + @Override + public String getName() { + return this.name(); + } + + @Override + public String getMessage() { + return this.message; + } +} diff --git a/src/main/java/gdsc/konkuk/platformcore/global/responses/ErrorResponse.java b/src/main/java/gdsc/konkuk/platformcore/global/responses/ErrorResponse.java index 4287781..9022c28 100644 --- a/src/main/java/gdsc/konkuk/platformcore/global/responses/ErrorResponse.java +++ b/src/main/java/gdsc/konkuk/platformcore/global/responses/ErrorResponse.java @@ -1,6 +1,6 @@ package gdsc.konkuk.platformcore.global.responses; -import gdsc.konkuk.platformcore.global.exceptions.ErrorCode; +import gdsc.konkuk.platformcore.global.exceptions.CustomErrorCode; import lombok.Getter; @Getter @@ -13,12 +13,12 @@ private ErrorResponse(final String message, final String errorCode) { this.errorCode = errorCode; } - private ErrorResponse(final ErrorCode errorCode) { + private ErrorResponse(final CustomErrorCode errorCode) { super(false, errorCode.getMessage()); - this.errorCode = errorCode.name(); + this.errorCode = errorCode.getName(); } - public static ErrorResponse of(ErrorCode errorCode) { + public static ErrorResponse of(CustomErrorCode errorCode) { return new ErrorResponse(errorCode); } diff --git a/src/test/java/gdsc/konkuk/platformcore/application/member/MemberServiceTest.java b/src/test/java/gdsc/konkuk/platformcore/application/member/MemberServiceTest.java new file mode 100644 index 0000000..b0ac620 --- /dev/null +++ b/src/test/java/gdsc/konkuk/platformcore/application/member/MemberServiceTest.java @@ -0,0 +1,82 @@ +package gdsc.konkuk.platformcore.application.member; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.*; + +import java.util.Optional; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.security.crypto.password.PasswordEncoder; + +import gdsc.konkuk.platformcore.application.member.exceptions.UserAlreadyExistException; +import gdsc.konkuk.platformcore.controller.member.MemberRegisterRequest; +import gdsc.konkuk.platformcore.domain.member.repository.MemberRepository; +import gdsc.konkuk.platformcore.global.responses.SuccessResponse; + +class MemberServiceTest { + + private MemberService subject; + + @Mock + private MemberRepository memberRepository; + + @Mock + private PasswordEncoder passwordEncoder; + + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + subject = new MemberService(passwordEncoder, memberRepository); + } + + @Test + @DisplayName("새로운 멤버 회원가입 성공") + void should_success_when_newMember_register() { + //given + MemberRegisterRequest memberRegisterRequest = + MemberRegisterRequest.builder() + .memberId("202011288") + .password("password") + .email("example@konkuk.ac.kr") + .name("홍길동") + .batch(2024) + .build(); + given(memberRepository.findByMemberId(any())).willReturn(Optional.empty()); + given(passwordEncoder.encode(any())).willReturn("password"); + //when + SuccessResponse expected = SuccessResponse.messageOnly(); + SuccessResponse actual = subject.register(memberRegisterRequest); + //then + assertEquals(expected.isSuccess(), actual.isSuccess()); + assertEquals(expected.getData(), actual.getData()); + } + + @Test + @DisplayName("이미 존재하는 멤버 회원가입 실패") + void should_fail_when_already_exist_member_register() { + //given + MemberRegisterRequest memberRegisterRequest = + MemberRegisterRequest.builder() + .memberId("202011288") + .password("password") + .email("example@konkuk.ac.kr") + .name("홍길동") + .batch(2024) + .build(); + given(memberRepository.findByMemberId(any())) + .willReturn(Optional.of(MemberRegisterRequest + .toEntity(memberRegisterRequest))); + + //then + assertThrows(UserAlreadyExistException.class, () -> { + //when + subject.register(memberRegisterRequest); + }); + } +} \ No newline at end of file diff --git a/src/test/java/gdsc/konkuk/platformcore/controller/member/MemberControllerTest.java b/src/test/java/gdsc/konkuk/platformcore/controller/member/MemberControllerTest.java new file mode 100644 index 0000000..02ba01f --- /dev/null +++ b/src/test/java/gdsc/konkuk/platformcore/controller/member/MemberControllerTest.java @@ -0,0 +1,133 @@ +package gdsc.konkuk.platformcore.controller.member; + +import static com.epages.restdocs.apispec.ResourceDocumentation.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.*; +import static org.springframework.http.MediaType.*; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.*; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.MockitoAnnotations; +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.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; +import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import com.epages.restdocs.apispec.ResourceSnippetParameters; +import com.fasterxml.jackson.databind.ObjectMapper; + +import gdsc.konkuk.platformcore.application.member.MemberService; +import gdsc.konkuk.platformcore.application.member.exceptions.UserAlreadyExistException; +import gdsc.konkuk.platformcore.global.responses.SuccessResponse; + +@SpringBootTest +@ExtendWith({RestDocumentationExtension.class}) +class MemberControllerTest { + + MockMvc mockMvc; + + @MockBean + private MemberService memberService; + + @Autowired + private ObjectMapper objectMapper; + + @BeforeEach + void setUp(WebApplicationContext webApplicationContext, RestDocumentationContextProvider restDocumentation) { + MockitoAnnotations.openMocks(this); + mockMvc = MockMvcBuilders + .webAppContextSetup(webApplicationContext) + .apply(springSecurity()) + .apply(documentationConfiguration(restDocumentation)) + .build(); + } + + @Test + @DisplayName("새로운 멤버 회원 가입 성공") + void should_success_when_newMember() throws Exception { + //given + MemberRegisterRequest memberRegisterRequest = + MemberRegisterRequest.builder() + .memberId("202011288") + .password("password") + .email("example@konkuk.ac.kr") + .name("홍길동") + .batch(2024) + .build(); + + given(memberService.register(any(MemberRegisterRequest.class))).willReturn(SuccessResponse.messageOnly()); + //when + mockMvc.perform( + RestDocumentationRequestBuilders.post("/api/v1/members") + .contentType(APPLICATION_JSON) + .content(objectMapper.writeValueAsString(memberRegisterRequest)) + .with(csrf()) + + ) + + .andExpect(status().isCreated()) + .andDo(print()) + .andDo( + document("member/register", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + resource(ResourceSnippetParameters.builder() + .description("새로운 멤버 회원 가입 성공") + .tag("member") + .requestFields( + fieldWithPath("memberId").description("회원 아이디"), + fieldWithPath("password").description("비밀번호"), + fieldWithPath("email").description("이메일"), + fieldWithPath("name").description("이름"), + fieldWithPath("batch").description("배치") + ) + .responseFields( + fieldWithPath("success").description(true), + fieldWithPath("message").description("회원 가입 성공"), + fieldWithPath("data").description("null") + ) + .build() + ) + ) + ); + } + + @Test + @DisplayName("이미 존재하는 유저 회원 가입 실패") + void should_fail_when_existingMember() throws Exception { + //given + MemberRegisterRequest memberRegisterRequest = + MemberRegisterRequest.builder() + .memberId("202011288") + .password("password") + .email("example@konkuk.ac.kr") + .name("홍길동") + .batch(2024) + .build(); + + given(memberService.register(any(MemberRegisterRequest.class))).willThrow(UserAlreadyExistException.class); + //when + mockMvc.perform( + RestDocumentationRequestBuilders.post("/api/v1/members") + .contentType(APPLICATION_JSON) + .content(objectMapper.writeValueAsString(memberRegisterRequest)) + .with(csrf()) + ) + .andExpect(status().isBadRequest()) + .andDo(print()); + } +} \ No newline at end of file