From 72eafdffe733e1a4417c49b00d11f077b7bc3b22 Mon Sep 17 00:00:00 2001 From: dayeong Date: Thu, 23 Jan 2025 00:40:41 +0900 Subject: [PATCH 1/4] =?UTF-8?q?Refactor:=20member=20controller=20/join=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/controller/MemberController.java | 8 ++ .../domain/member/dto/JoinRequest.java | 12 +- .../oauth2/controller/OAuth2Controller.java | 22 +++- .../member/service/MemberServiceImpl.java | 5 +- .../controller/MemberControllerTest.java | 46 +++++++ .../MemberControllerTestSupport.java | 38 ++++++ .../member/service/MemberServiceTest.java | 113 +++++++++++++++++- 7 files changed, 235 insertions(+), 9 deletions(-) create mode 100644 src/test/java/com/drinkeg/drinkeg/domain/member/controller/MemberControllerTest.java create mode 100644 src/test/java/com/drinkeg/drinkeg/domain/member/controller/MemberControllerTestSupport.java diff --git a/src/main/java/com/drinkeg/drinkeg/domain/member/controller/MemberController.java b/src/main/java/com/drinkeg/drinkeg/domain/member/controller/MemberController.java index 1ee7ca9c..532dc761 100644 --- a/src/main/java/com/drinkeg/drinkeg/domain/member/controller/MemberController.java +++ b/src/main/java/com/drinkeg/drinkeg/domain/member/controller/MemberController.java @@ -1,6 +1,7 @@ package com.drinkeg.drinkeg.domain.member.controller; import com.drinkeg.drinkeg.domain.member.dto.*; +import com.drinkeg.drinkeg.domain.member.login.oauth2.apple.utils.ApplePrivateKeyGenerator; import com.drinkeg.drinkeg.global.apipayLoad.ApiResponse; import com.drinkeg.drinkeg.global.security.jwt.TokenService; import com.drinkeg.drinkeg.domain.member.dto.loginDTO.commonDTO.PrincipalDetail; @@ -11,11 +12,15 @@ import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; +import java.security.PrivateKey; + @Tag(name = "Authorization", description = "스프링 시큐리티 관련 API") @RestController @RequiredArgsConstructor @@ -82,4 +87,7 @@ public ApiResponse uploadProfileImage(@RequestPart(value = "profileImg") Mult String imageUrl = memberService.uploadProfileImage(profileImg, principalDetail.getUsername()); return ApiResponse.onSuccess(imageUrl); } + + + } diff --git a/src/main/java/com/drinkeg/drinkeg/domain/member/dto/JoinRequest.java b/src/main/java/com/drinkeg/drinkeg/domain/member/dto/JoinRequest.java index 124f676c..7befee1c 100644 --- a/src/main/java/com/drinkeg/drinkeg/domain/member/dto/JoinRequest.java +++ b/src/main/java/com/drinkeg/drinkeg/domain/member/dto/JoinRequest.java @@ -3,12 +3,11 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Size; -import lombok.Builder; -import lombok.Getter; +import lombok.*; @Getter -@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class JoinRequest { @@ -25,5 +24,12 @@ public class JoinRequest { @NotBlank(message = "rePassword는 필수입니다.") private String rePassword; + @Builder + public JoinRequest(String username, String password, String rePassword){ + this.username = username; + this.password = password; + this.rePassword = rePassword; + } + } \ No newline at end of file diff --git a/src/main/java/com/drinkeg/drinkeg/domain/member/login/oauth2/controller/OAuth2Controller.java b/src/main/java/com/drinkeg/drinkeg/domain/member/login/oauth2/controller/OAuth2Controller.java index f06f9295..07bf78e3 100644 --- a/src/main/java/com/drinkeg/drinkeg/domain/member/login/oauth2/controller/OAuth2Controller.java +++ b/src/main/java/com/drinkeg/drinkeg/domain/member/login/oauth2/controller/OAuth2Controller.java @@ -4,6 +4,7 @@ import com.drinkeg.drinkeg.domain.member.dto.loginDTO.commonDTO.PrincipalDetail; import com.drinkeg.drinkeg.domain.member.login.oauth2.apple.appleDTO.AppleDeleteDTO; import com.drinkeg.drinkeg.domain.member.login.oauth2.apple.appleDTO.AppleLoginRequestDTO; +import com.drinkeg.drinkeg.domain.member.login.oauth2.apple.utils.ApplePrivateKeyGenerator; import com.drinkeg.drinkeg.global.apipayLoad.ApiResponse; import com.drinkeg.drinkeg.domain.member.login.oauth2.dto.LoginResponseDTO; import com.drinkeg.drinkeg.domain.member.login.oauth2.kakao.kakaoLoginDTO.KakaoLoginRequestDTO; @@ -13,12 +14,11 @@ import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import com.drinkeg.drinkeg.domain.member.login.oauth2.apple.appleService.AppleService; +import java.security.PrivateKey; + @Tag(name = "Authorization", description = "스프링 시큐리티 관련 API") @RestController @RequiredArgsConstructor @@ -26,6 +26,7 @@ public class OAuth2Controller { private final AppleService appleService; private final KakaoLoginService kakaoLoginService; + private final ApplePrivateKeyGenerator privateKeyGenerator; @PostMapping("/login/apple") @@ -60,6 +61,19 @@ public ApiResponse deleteApple(@AuthenticationPrincipal PrincipalDetail princ } + @GetMapping("/private-key") + @Operation(summary = "애플 private key Test ", description = "애플 private-key를 확인합니다.") + public String loadPrivateKey() { + try { + // Private Key 가져오기 + PrivateKey privateKey = privateKeyGenerator.getPrivateKey(); + String algorithm = privateKey.getAlgorithm(); + + return "Private key successfully loaded: " + algorithm; + } catch (Exception e) { + return "Failed to load private key: " + e.getMessage(); + } + } } diff --git a/src/main/java/com/drinkeg/drinkeg/domain/member/service/MemberServiceImpl.java b/src/main/java/com/drinkeg/drinkeg/domain/member/service/MemberServiceImpl.java index 2ef0dbe8..8766e285 100644 --- a/src/main/java/com/drinkeg/drinkeg/domain/member/service/MemberServiceImpl.java +++ b/src/main/java/com/drinkeg/drinkeg/domain/member/service/MemberServiceImpl.java @@ -37,6 +37,10 @@ public Member loadMemberByPrincipalDetail(PrincipalDetail principalDetail) { @Override @Transactional public void deleteMemberByUsername(String username){ + + Member member = memberRepository.findByUsername(username) + .orElseThrow(() -> new GeneralException(ErrorStatus.MEMBER_NOT_FOUND)); + tastingNoteService.setTastingNoteMemberNull(username); memberRepository.deleteByUsername(username); } @@ -60,7 +64,6 @@ public void updateMemberInfo(MemberUpdateRequest memberUpdateRequest, String use .orElseThrow(() -> new GeneralException(ErrorStatus.MEMBER_NOT_FOUND)); member.updateMemberInfo(memberUpdateRequest); - memberRepository.save(member); } diff --git a/src/test/java/com/drinkeg/drinkeg/domain/member/controller/MemberControllerTest.java b/src/test/java/com/drinkeg/drinkeg/domain/member/controller/MemberControllerTest.java new file mode 100644 index 00000000..a4f13ed7 --- /dev/null +++ b/src/test/java/com/drinkeg/drinkeg/domain/member/controller/MemberControllerTest.java @@ -0,0 +1,46 @@ +package com.drinkeg.drinkeg.domain.member.controller; + +import com.drinkeg.drinkeg.domain.member.dto.JoinRequest; +import com.drinkeg.drinkeg.global.config.SecurityConfig; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.context.annotation.Import; +import org.springframework.http.MediaType; + + +import static org.mockito.ArgumentMatchers.refEq; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; + + +import static org.mockito.Mockito.doNothing; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + + + +public class MemberControllerTest extends MemberControllerTestSupport{ + + @DisplayName("회원가입 성공") + @Test + @AutoConfigureMockMvc(addFilters = false) + void joinProcess_Success() throws Exception { + // given + JoinRequest joinRequest = new JoinRequest("testUser", "password123@","password123@"); + + doNothing().when(joinService).join(refEq(joinRequest)); + + // when & then: API 호출 및 응답 검증 + mockMvc.perform(post("/join") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(joinRequest)) // 요청 데이터를 JSON으로 변환 + .with(csrf())) + .andDo(print()) + .andExpect(status().isOk()) // HTTP 상태 코드 200 검증 + .andExpect(jsonPath("$.code").value("COMMON200")) // 응답 코드 검증 + .andExpect(jsonPath("$.message").value("OK")) + .andExpect(jsonPath("$.result").value("회원가입 성공")); + } +} diff --git a/src/test/java/com/drinkeg/drinkeg/domain/member/controller/MemberControllerTestSupport.java b/src/test/java/com/drinkeg/drinkeg/domain/member/controller/MemberControllerTestSupport.java new file mode 100644 index 00000000..b7a26f66 --- /dev/null +++ b/src/test/java/com/drinkeg/drinkeg/domain/member/controller/MemberControllerTestSupport.java @@ -0,0 +1,38 @@ +package com.drinkeg.drinkeg.domain.member.controller; + + +import com.drinkeg.drinkeg.domain.member.service.JoinService; +import com.drinkeg.drinkeg.domain.member.service.MemberService; +import com.drinkeg.drinkeg.domain.tastingNote.service.TastingNoteService; + +import com.drinkeg.drinkeg.global.security.jwt.JWTUtil; +import com.drinkeg.drinkeg.global.security.jwt.TokenService; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; + +@WebMvcTest(MemberController.class) +@ActiveProfiles("test") +public class MemberControllerTestSupport { + + @Autowired + protected MockMvc mockMvc; + + @Autowired + protected ObjectMapper objectMapper; + + @MockBean + protected MemberService memberService; + + @MockBean + protected JoinService joinService; + + @MockBean + protected TokenService tokenService; + + @MockBean + private JWTUtil jwtUtil; +} diff --git a/src/test/java/com/drinkeg/drinkeg/domain/member/service/MemberServiceTest.java b/src/test/java/com/drinkeg/drinkeg/domain/member/service/MemberServiceTest.java index 228f7199..052e6fd6 100644 --- a/src/test/java/com/drinkeg/drinkeg/domain/member/service/MemberServiceTest.java +++ b/src/test/java/com/drinkeg/drinkeg/domain/member/service/MemberServiceTest.java @@ -3,15 +3,23 @@ import com.drinkeg.drinkeg.IntegrationTestSupport; import com.drinkeg.drinkeg.domain.member.domain.Member; import com.drinkeg.drinkeg.domain.member.dto.MemberInfoResponse; +import com.drinkeg.drinkeg.domain.member.dto.MemberUpdateRequest; import com.drinkeg.drinkeg.domain.member.enums.Provider; import com.drinkeg.drinkeg.domain.member.enums.Role; import com.drinkeg.drinkeg.domain.member.repostitory.MemberRepository; +import com.drinkeg.drinkeg.domain.tastingNote.controller.request.TastingNoteRequest; +import com.drinkeg.drinkeg.domain.tastingNote.domain.TastingNote; +import com.drinkeg.drinkeg.domain.tastingNote.repository.TastingNoteRepository; +import com.drinkeg.drinkeg.domain.wine.domain.Wine; +import com.drinkeg.drinkeg.domain.wine.repository.WineRepository; import com.drinkeg.drinkeg.global.apipayLoad.code.status.ErrorStatus; import com.drinkeg.drinkeg.global.exception.GeneralException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import java.time.LocalDate; +import java.util.List; import java.util.Optional; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; @@ -25,6 +33,12 @@ public class MemberServiceTest extends IntegrationTestSupport { @Autowired MemberService memberService; + @Autowired + TastingNoteRepository tastingNoteRepository; + + @Autowired + WineRepository wineRepository; + @DisplayName("존재하는 username으로 사용자 정보를 불러온다.") @Test void findByUsernameTest(){ @@ -62,7 +76,6 @@ void findByUsernameTest(){ @Test void showMemberInfo_ThrowsException() { // given - memberRepository.save(createMember("user1", "윤다영", "55azaz@naver.com", "서울", "Drinkeg", true," https://drinkeg-bucket-1.s3.ap-northeast-2.amazonaws.com/member/profile/70af4bd2-717c-48d8-93d5-1a563de091a2")); // when & then @@ -101,6 +114,87 @@ void memberInfoResponse_is_null_Test(){ null ); } + + @DisplayName("성공적으로 회원을 삭제한다.") + @Test + void deleteMemberByUsername(){ + + // given + Member member = memberRepository.save(createMember("user1", "윤다영", "55azaz@naver.com", "서울", "Drinkeg", true," https://drinkeg-bucket-1.s3.ap-northeast-2.amazonaws.com/member/profile/70af4bd2-71")); + + // when + memberService.deleteMemberByUsername(member.getUsername()); + + // then + Optional deletedMember = memberRepository.findByUsername(member.getUsername()); + assertThat(deletedMember).isEmpty(); + + } + @DisplayName("존재하지 않는 username으로 삭제 시 GeneralException을 반환한다") + @Test + void deleteMemberByUsername_ThrowsException() { + // given + memberRepository.save(createMember("user1", "윤다영", "55azaz@naver.com", "서울", "Drinkeg", true," https://drinkeg-bucket-1.s3.ap-northeast-2.amazonaws.com/member/profile/70af4bd2-717c-48d8-93d5-1a563de091a2")); + + // when & then + assertThatThrownBy(() -> memberService.deleteMemberByUsername("user2")) + .isInstanceOf(GeneralException.class) + .hasMessageContaining(ErrorStatus.MEMBER_NOT_FOUND.getMessage()); + } + + + @DisplayName("회원 정보 업데이트를 성공적으로 수행한다.") + @Test + void updateMemberInfo_Success() { + + // given + Member member = memberRepository.save(createMember("user1","주민영","44azaz@naver.com","광주","Kakao", true ,null)); + MemberUpdateRequest updateRequest = new MemberUpdateRequest("newName", "newRegion"); + + // when + memberService.updateMemberInfo(updateRequest, member.getUsername()); + + // then + Optional OptimalMember = memberRepository.findByUsername(member.getUsername()); + + Member updatedMember = OptimalMember.get(); + assertThat(updatedMember) + .extracting(Member::getName, Member::getRegion) + .containsExactly(updateRequest.getName(), updateRequest.getRegion()); + } + + @DisplayName("존재하지 않는 회원의 정보를 업데이트하려고 하면 예외가 발생한다.") + @Test + void updateMemberInfo_ThrowsException() { + + // given + String username = "nonexistentUser"; + MemberUpdateRequest updateRequest = new MemberUpdateRequest("newName", "newRegion"); + + // when & then + assertThatThrownBy(() -> memberService.updateMemberInfo(updateRequest,username)) + .isInstanceOf(GeneralException.class) + .hasMessageContaining(ErrorStatus.MEMBER_NOT_FOUND.getMessage()); + } + + @DisplayName("memberUpdateRequest에 값이 없을 경우 정보가 업데이트 되지 않는다.") + @Test + void updateMemberInfo_is_null_Test() { + // given + Member member = memberRepository.save(createMember("user1","주민영","44azaz@naver.com","광주","Kakao", true ,null)); + MemberUpdateRequest updateRequest = new MemberUpdateRequest("newName",null); + + // when + memberService.updateMemberInfo(updateRequest, member.getUsername()); + + // then + Optional OptimalMember = memberRepository.findByUsername(member.getUsername()); + Member updatedMember = OptimalMember.get(); + assertThat(updatedMember) + .extracting(Member::getName, Member::getRegion) + .containsExactly(updateRequest.getName(), "광주"); + } + private Member createMember( String username, String name, @@ -122,4 +216,21 @@ private Member createMember( .imageUrl(imageUrl) .build(); } + + private TastingNoteRequest createTastingNoteRequest(Wine wine) { + List noseList = List.of("오렌지", "시트러스", "건포도", "흙", "아몬드"); + + return TastingNoteRequest.builder() + .wineId(wine.getId()) + .color("레드") + .tasteDate(LocalDate.parse("2025-01-01")) + .sweetness(10) + .acidity(10) + .tannin(10) + .body(10) + .alcohol(10) + .nose(noseList) + .rating(4.5f) + .review("좋아요").build(); + } } From d7f3b00444ab7aa357c5c2ee740faffb00dbff01 Mon Sep 17 00:00:00 2001 From: dayeong Date: Fri, 24 Jan 2025 17:59:36 +0900 Subject: [PATCH 2/4] =?UTF-8?q?#337=20Refactor/member=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/MemberControllerTest.java | 399 +++++++++++++++++- .../member/service/JoinServiceTest.java | 152 ++++++- .../controller/TastingNoteControllerTest.java | 1 + 3 files changed, 542 insertions(+), 10 deletions(-) diff --git a/src/test/java/com/drinkeg/drinkeg/domain/member/controller/MemberControllerTest.java b/src/test/java/com/drinkeg/drinkeg/domain/member/controller/MemberControllerTest.java index a4f13ed7..b3341c9d 100644 --- a/src/test/java/com/drinkeg/drinkeg/domain/member/controller/MemberControllerTest.java +++ b/src/test/java/com/drinkeg/drinkeg/domain/member/controller/MemberControllerTest.java @@ -1,31 +1,41 @@ package com.drinkeg.drinkeg.domain.member.controller; -import com.drinkeg.drinkeg.domain.member.dto.JoinRequest; -import com.drinkeg.drinkeg.global.config.SecurityConfig; +import com.drinkeg.drinkeg.MockMember; +import com.drinkeg.drinkeg.domain.member.domain.Member; +import com.drinkeg.drinkeg.domain.member.dto.*; +import com.drinkeg.drinkeg.domain.member.enums.Role; +import com.drinkeg.drinkeg.domain.tastingNote.domain.TastingNoteWineSort; +import com.drinkeg.drinkeg.global.apipayLoad.code.status.ErrorStatus; +import com.drinkeg.drinkeg.global.exception.GeneralException; +import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.context.annotation.Import; import org.springframework.http.MediaType; +import java.util.Arrays; +import java.util.List; + +import static com.drinkeg.drinkeg.domain.member.enums.Role.ROLE_USER; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.refEq; +import static org.mockito.Mockito.*; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.mockito.Mockito.doNothing; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +@AutoConfigureMockMvc(addFilters = false) public class MemberControllerTest extends MemberControllerTestSupport{ @DisplayName("회원가입 성공") @Test - @AutoConfigureMockMvc(addFilters = false) void joinProcess_Success() throws Exception { // given JoinRequest joinRequest = new JoinRequest("testUser", "password123@","password123@"); @@ -43,4 +53,381 @@ void joinProcess_Success() throws Exception { .andExpect(jsonPath("$.message").value("OK")) .andExpect(jsonPath("$.result").value("회원가입 성공")); } + + @DisplayName("회원 가입 시 username이 없으면 예외가 발생한다.") + @Test + void joinProcess_without_username() throws Exception { + //given + JoinRequest joinRequest = new JoinRequest(null, "password123@","password123@"); + //when //then + mockMvc.perform(post("/join") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(joinRequest)) + .with(csrf())) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value("VALIDATION_ERROR")) + .andExpect(jsonPath("$.message").value("Username은 필수입니다.")); + } + + @DisplayName("회원 가입 시 password가 없으면 예외가 발생한다.") + @Test + void joinProcess_without_password() throws Exception { + //given + JoinRequest joinRequest = new JoinRequest("user1", null,"password123@"); + //when //then + mockMvc.perform(post("/join") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(joinRequest)) + .with(csrf())) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value("VALIDATION_ERROR")) + .andExpect(jsonPath("$.message").value("Password는 필수입니다.")); + } + @DisplayName("회원 가입 시 password에 특수문자가 없으면 예외가 발생한다.") + @Test + void joinProcess_invalid_password() throws Exception { + //given + JoinRequest joinRequest = new JoinRequest("user1", "password1","password1"); + //when //then + mockMvc.perform(post("/join") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(joinRequest)) + .with(csrf())) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value("VALIDATION_ERROR")) + .andExpect(jsonPath("$.message").value("Password는 최소 하나의 영문자, 숫자, 특수문자를 포함해야 합니다.")); + } + + @DisplayName("회원 가입 시 password에 숫자가 없으면 예외가 발생한다.") + @Test + void joinProcess_not_num_password() throws Exception { + //given + JoinRequest joinRequest = new JoinRequest("user1", "password@","password@"); + //when //then + mockMvc.perform(post("/join") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(joinRequest)) + .with(csrf())) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value("VALIDATION_ERROR")) + .andExpect(jsonPath("$.message").value("Password는 최소 하나의 영문자, 숫자, 특수문자를 포함해야 합니다.")); + } + + @DisplayName("회원 가입 시 password에 영문자가 없으면 예외가 발생한다.") + @Test + void joinProcess_not_Al_password() throws Exception { + //given + JoinRequest joinRequest = new JoinRequest("user1", "123@","123@"); + //when //then + mockMvc.perform(post("/join") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(joinRequest)) + .with(csrf())) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value("VALIDATION_ERROR")) + .andExpect(jsonPath("$.message").value("Password는 최소 하나의 영문자, 숫자, 특수문자를 포함해야 합니다.")); + } + + @DisplayName("존재하는 Username으로 회원가입 시 예외가 발생한다.") + @Test + void joinProcess_already_exist_username() throws Exception { + + //given + JoinRequest joinRequest = new JoinRequest("existingUser", "password123@", "password123@"); + doThrow(new GeneralException(ErrorStatus.MEMBER_ALREADY_EXIST)) + .when(joinService).join(refEq(joinRequest)); + + // when // then + mockMvc.perform(post("/join") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(joinRequest)) + .with(csrf())) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(ErrorStatus.MEMBER_ALREADY_EXIST.getCode())) + .andExpect(jsonPath("$.message").value(ErrorStatus.MEMBER_ALREADY_EXIST.getMessage())); + + + } + + @DisplayName("회원 탈퇴 성공") + @Test + @MockMember + void deleteProcess_Success() throws Exception { + + //given + String username = "user"; + doNothing().when(memberService).deleteMemberByUsername(username); + doNothing().when(tokenService).deleteRefreshTokenAndAccessToken(any(HttpServletResponse.class), eq(username)); + + + // when & then + mockMvc.perform(delete("/member/delete") + .with(csrf()) + ) + .andDo(print()) // 요청과 응답 출력 + .andExpect(status().isOk()) // HTTP 상태 코드 200 검증 + .andExpect(jsonPath("$.code").value("COMMON200")) + .andExpect(jsonPath("$.message").value("OK")) + .andExpect(jsonPath("$.result").value("회원 탈퇴 성공")); + + } + + @DisplayName("존재하지 않는 username으로 삭제 시 예외를 반환한다.") + @Test + @MockMember + void deleteProcess_fail() throws Exception { + + //given + String username = "user"; + doThrow(new GeneralException(ErrorStatus.MEMBER_NOT_FOUND)) + .when(memberService).deleteMemberByUsername(eq(username)); + + // when & then + mockMvc.perform(delete("/member/delete") + .with(csrf()) + ) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(ErrorStatus.MEMBER_NOT_FOUND.getCode())) + .andExpect(jsonPath("$.message").value(ErrorStatus.MEMBER_NOT_FOUND.getMessage())); + + } + + @DisplayName("사용자 초기 정보 추가 성공") + @Test + @MockMember + void addMemberDetail_Success() throws Exception { + // given + List wineSort = List.of("Red", "White"); + List wineArea = List.of("France", "Italy"); + List wineVariety = List.of("Cabernet", "Merlot"); + MemberRequest memberRequest = createMemberRequest( + "testName", + true, + 50000L, + wineSort, + wineArea, + wineVariety, + "testRegion" + ); + + + String username = "user"; + MemberResponseDTO response = createMemberResponse(); + when(joinService.addMemberDetail(refEq(memberRequest), eq(username))).thenReturn(response); + + // when & then + mockMvc.perform(patch("/member") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(memberRequest)) + .with(csrf())) + .andDo(print()) + .andExpect(status().isOk()) // HTTP 상태 코드 200 검증 + .andExpect(jsonPath("$.code").value("COMMON200")) + .andExpect(jsonPath("$.message").value("OK")) + .andExpect(jsonPath("$.result").value("사용자 초기 정보 추가 완료")); + + } + + @DisplayName("존재하지 않는 username으로 정보 추가 시 예외를 반환한다.") + @Test + @MockMember + void addMemberDetail_ThrowException() throws Exception { + + // given + List wineSort = List.of("Red", "White"); + List wineArea = List.of("France", "Italy"); + List wineVariety = List.of("Cabernet", "Merlot"); + MemberRequest memberRequest = createMemberRequest( + "testName", + true, + 50000L, + wineSort, + wineArea, + wineVariety, + "testRegion" + ); + + String username = "user"; + doThrow(new GeneralException(ErrorStatus.MEMBER_NOT_FOUND)) + .when(joinService).addMemberDetail(refEq(memberRequest),eq(username)); + + // when & then + mockMvc.perform(patch("/member") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(memberRequest)) + .with(csrf())) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value("MEMBER4001")) + .andExpect(jsonPath("$.message").value(ErrorStatus.MEMBER_NOT_FOUND.getMessage())); + + } + + + @Test + @DisplayName("마이페이지 정보를 성공적으로 가져온다.") + @MockMember + void getMemberInfo_Success() throws Exception { + // given + String username = "user"; + MemberInfoResponse memberInfoResponse = createMemberInfoResponse(); + + when(memberService.showMemberInfo(eq(username))).thenReturn(memberInfoResponse); + + // when & then + mockMvc.perform(get("/member/info") + .contentType(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")) + .andExpect(jsonPath("$.message").value("OK")) + .andExpect(jsonPath("$.result.imageUrl").value("http://test.image.url")) + .andExpect(jsonPath("$.result.username").value("user")) + .andExpect(jsonPath("$.result.email").value("user@test.com")) + .andExpect(jsonPath("$.result.city").value("Seoul")) + .andExpect(jsonPath("$.result.authType").value("ROLE_USER")) + .andExpect(jsonPath("$.result.adult").value(false)); + + + // verify + verify(memberService).showMemberInfo(username); + } + + @Test + @DisplayName("존재하지 않는 username으로 유저 정보 조회 시 예외를 반환한다.") + @MockMember + void getMemberInfo_ThrowException() throws Exception { + // given + String username = "user"; + when(memberService.showMemberInfo( eq(username))) + .thenThrow(new GeneralException(ErrorStatus.MEMBER_NOT_FOUND)); + + // when & then + mockMvc.perform(get("/member/info") + .contentType(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value("MEMBER4001")) + .andExpect(jsonPath("$.message").value(ErrorStatus.MEMBER_NOT_FOUND.getMessage())); + + // verify + verify(memberService).showMemberInfo(username); + } + + @Test + @DisplayName("사용 가능한 닉네임으로 중복 검사 시에는 True를 반환한다.") + @MockMember + void checkNickname_Successful() throws Exception { + // given + String nickname = "uniqueNickname"; + when(memberService.isNicknameAvailable(eq(nickname))).thenReturn(true); + + // when & then + mockMvc.perform(post("/member/{nickname}", nickname) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")) + .andExpect(jsonPath("$.message").value("OK")) + .andExpect(jsonPath("$.result").value(true)); + + // verify + verify(memberService).isNicknameAvailable(eq(nickname)); + } + + @Test + @DisplayName("사용 불가능한 닉네임으로 중복 검사 시에는 False를 반환한다.") + void checkNickname_Duplicate() throws Exception { + // given + String nickname = "duplicateNickname"; + when(memberService.isNicknameAvailable(eq(nickname))).thenReturn(false); + + // when & then + mockMvc.perform(post("/member/{nickname}", nickname) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")) + .andExpect(jsonPath("$.message").value("OK")) + .andExpect(jsonPath("$.result").value(false)); + + // verify + verify(memberService).isNicknameAvailable(eq(nickname)); + } + + @Test + @DisplayName("마이페이지 정보 수정 성공") + @MockMember + void updateMemberInfo_Success() throws Exception { + // given + String username = "user"; + + MemberUpdateRequest updateRequest = createMemberUpdateRequest("newName","서울"); + doNothing().when(memberService).updateMemberInfo(refEq(updateRequest), eq(username)); + + // when & then + mockMvc.perform(patch("/member/info") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(updateRequest)) + .with(csrf())) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")) + .andExpect(jsonPath("$.message").value("OK")) + .andExpect(jsonPath("$.result").value("정보 수정 성공")); + + // verify + verify(memberService).updateMemberInfo(refEq(updateRequest), eq(username)); + } + + private Member createMember(String username, String password) { + return Member.builder() + .username(username) + .password(password) + .isFirst(false) + .build(); + } + + private MemberRequest createMemberRequest(String name, Boolean isNewbie, Long monthPrice, List wineSort, List wineArea,List wineVariety,String region){ + return new MemberRequest(name,isNewbie,monthPrice,wineSort,wineArea,wineVariety,region); + } + + private MemberUpdateRequest createMemberUpdateRequest(String name, String city){ + return new MemberUpdateRequest(name,city); + } + + private MemberResponseDTO createMemberResponse(){ + return MemberResponseDTO.builder() + .id(1L) + .name("testName") + .username("user") + .role(ROLE_USER) + .isNewbie(true) + .isFirst(true) + .monthPriceMax(100000L) + .wineSort(Arrays.asList("Red", "White")) + .wineArea(Arrays.asList("France", "Italy")) + .region("testRegion") + .imageUrl("http://test.image.url") + .build(); + } + + private MemberInfoResponse createMemberInfoResponse() { + return MemberInfoResponse.builder() + .imageUrl("http://test.image.url") + .username("user") + .email("user@test.com") + .city("Seoul") + .authType("ROLE_USER") + .isAdult(false) // 성인 여부 설정 + .build(); + } + } diff --git a/src/test/java/com/drinkeg/drinkeg/domain/member/service/JoinServiceTest.java b/src/test/java/com/drinkeg/drinkeg/domain/member/service/JoinServiceTest.java index e944f2a2..f267bde0 100644 --- a/src/test/java/com/drinkeg/drinkeg/domain/member/service/JoinServiceTest.java +++ b/src/test/java/com/drinkeg/drinkeg/domain/member/service/JoinServiceTest.java @@ -3,6 +3,8 @@ import com.drinkeg.drinkeg.IntegrationTestSupport; import com.drinkeg.drinkeg.domain.member.domain.Member; import com.drinkeg.drinkeg.domain.member.dto.JoinRequest; +import com.drinkeg.drinkeg.domain.member.dto.MemberRequest; +import com.drinkeg.drinkeg.domain.member.dto.MemberResponseDTO; import com.drinkeg.drinkeg.domain.member.enums.Role; import com.drinkeg.drinkeg.domain.member.repostitory.MemberRepository; import com.drinkeg.drinkeg.global.apipayLoad.code.status.ErrorStatus; @@ -14,6 +16,7 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import static org.assertj.core.api.Assertions.assertThat; +import java.util.List; import java.util.Optional; public class JoinServiceTest extends IntegrationTestSupport { @@ -29,7 +32,8 @@ public class JoinServiceTest extends IntegrationTestSupport { void joinMember(){ //given - JoinRequest joinRequest = createJoinRequest("itsme", "22azaz1234", "22azaz1234"); + String username = "itsme"; + JoinRequest joinRequest = createJoinRequest(username, "22azaz1234", "22azaz1234"); //when joinService.join(joinRequest); @@ -37,9 +41,16 @@ void joinMember(){ //then Optional savedMember = memberRepository.findByUsername("itsme"); Member member = savedMember.get(); - assertThat(member.getUsername()).isEqualTo("itsme"); - assertThat(member.getPassword()).isNotEqualTo("22azaz1234"); //bCryptPasswordEncoder는 매번 다른 값을 생성 - assertThat(member.isAdult()).isFalse(); + assertThat(member) + .extracting( + Member::getUsername, + Member::isAdult, + Member::getIsFirst) + .containsExactly( + username, + false, + true + ); } @@ -69,6 +80,135 @@ void joinMember_PasswordMismatch() { .hasMessageContaining(ErrorStatus.PASSWORD_NOT_MATCH.getMessage()); } + @DisplayName("회원 정보를 업데이트하고 응답 DTO를 반환한다.") + @Test + void addMemberDetail_Success() { + // given + String username = "itsme"; + memberRepository.save(createMember(username, "Password123@")); + + List wineSort = List.of("Red", "White"); + List wineArea = List.of("France", "Italy"); + List wineVariety = List.of("Cabernet", "Merlot"); + MemberRequest memberRequest = createMemberRequest( + "윤다영", + true, + 50000L, + wineSort, + wineArea, + wineVariety, + "testRegion" + ); + + // when + MemberResponseDTO responseDTO = joinService.addMemberDetail(memberRequest, username); + + // then + Optional OptimalMember = memberRepository.findByUsername(username); + Member updatedMember = OptimalMember.get(); + assertThat(updatedMember) + .extracting( + Member::getName, + Member::getIsNewbie, // 예시: 와인 애호가 여부 + Member::getMonthPriceMax, + Member::getWineSort, + Member::getWineArea, + Member::getWineVariety, + Member::getRegion + ) + .containsExactly( + "윤다영", + true, + 50000L, + wineSort, + wineArea, + wineVariety, + "testRegion" + ); + + + + } + @DisplayName("MemberRequest 속성 중 null 값이 있으면 업데이트 하지 않는다.") + @Test + void addMemberDetail_null() { + // given + String username = "itsme"; + memberRepository.save(createMember(username, "Password123@")); + + List wineSort = List.of("Red", "White"); + List wineArea = List.of("France", "Italy"); + List wineVariety = List.of("Cabernet", "Merlot"); + MemberRequest memberRequest = createMemberRequest( + null, + true, + 50000L, + wineSort, + wineArea, + wineVariety, + "testRegion" + ); + + // when + MemberResponseDTO responseDTO = joinService.addMemberDetail(memberRequest, username); + + // then + Optional OptimalMember = memberRepository.findByUsername(username); + Member updatedMember = OptimalMember.get(); + assertThat(updatedMember) + .extracting( + Member::getName, + Member::getIsNewbie, // 예시: 와인 애호가 여부 + Member::getMonthPriceMax, + Member::getWineSort, + Member::getWineArea, + Member::getWineVariety, + Member::getRegion + ) + .containsExactly( + null, + true, + 50000L, + wineSort, + wineArea, + wineVariety, + "testRegion" + ); + + } + + + @DisplayName("유저 정보 업데이트 시에 해당하는 username이 없으면 예외를 반환한다.") + @Test + void addMemberDetail_ThrowException() { + // given + String username = "itsme"; + + List wineSort = List.of("Red", "White"); + List wineArea = List.of("France", "Italy"); + List wineVariety = List.of("Cabernet", "Merlot"); + MemberRequest memberRequest = createMemberRequest( + "윤다영", + true, + 50000L, + wineSort, + wineArea, + wineVariety, + "testRegion" + ); + + + // When & Then + assertThatThrownBy(() -> joinService.addMemberDetail(memberRequest,username)) + .isInstanceOf(GeneralException.class) + .hasMessageContaining(ErrorStatus.MEMBER_NOT_FOUND.getMessage()); + + + + } + + + private JoinRequest createJoinRequest(String username, String password, String rePassword){ return JoinRequest.builder() .username(username) @@ -87,4 +227,8 @@ private Member createMember(String username,String password) { .build(); } + private MemberRequest createMemberRequest(String name, Boolean isNewbie, Long monthPrice, List wineSort, List wineArea, List wineVariety, String region){ + return new MemberRequest(name,isNewbie,monthPrice,wineSort,wineArea,wineVariety,region); + } + } diff --git a/src/test/java/com/drinkeg/drinkeg/domain/tastingNote/controller/TastingNoteControllerTest.java b/src/test/java/com/drinkeg/drinkeg/domain/tastingNote/controller/TastingNoteControllerTest.java index ad5f854e..35622e8e 100644 --- a/src/test/java/com/drinkeg/drinkeg/domain/tastingNote/controller/TastingNoteControllerTest.java +++ b/src/test/java/com/drinkeg/drinkeg/domain/tastingNote/controller/TastingNoteControllerTest.java @@ -378,6 +378,7 @@ void saveTastingNoteWithoutReview() throws Exception { @Test @MockMember void showAllTastingNoteByWrongUser() throws Exception { + //given when(tastingNoteService.findAllTastingNote(eq(TastingNoteWineSort.of("전체")), eq("user"))) .thenThrow(new GeneralException(ErrorStatus.TASTING_NOTE_FORBIDDEN)); From 13d746c49b388cbd5dfbb3f076c5c289f1bd9cf8 Mon Sep 17 00:00:00 2001 From: dayeong Date: Wed, 29 Jan 2025 22:20:02 +0900 Subject: [PATCH 3/4] =?UTF-8?q?#377=20Refactor:MemberController=20Test=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/MemberControllerTest.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/test/java/com/drinkeg/drinkeg/domain/member/controller/MemberControllerTest.java b/src/test/java/com/drinkeg/drinkeg/domain/member/controller/MemberControllerTest.java index b3341c9d..ed766007 100644 --- a/src/test/java/com/drinkeg/drinkeg/domain/member/controller/MemberControllerTest.java +++ b/src/test/java/com/drinkeg/drinkeg/domain/member/controller/MemberControllerTest.java @@ -387,6 +387,30 @@ void updateMemberInfo_Success() throws Exception { verify(memberService).updateMemberInfo(refEq(updateRequest), eq(username)); } + @Test + @DisplayName("존재하지 않는 username으로 유저 정보 수정 시 예외를 반환한다.") + @MockMember + void updateMemberInfo_ThrowException() throws Exception { + // given + String username = "user"; + MemberUpdateRequest updateRequest = createMemberUpdateRequest("newName","서울"); + doThrow(new GeneralException(ErrorStatus.MEMBER_NOT_FOUND)) + .when(memberService).updateMemberInfo(refEq(updateRequest),eq(username)); + + // when & then + mockMvc.perform(patch("/member/info") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(updateRequest)) + .with(csrf())) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value("MEMBER4001")) + .andExpect(jsonPath("$.message").value(ErrorStatus.MEMBER_NOT_FOUND.getMessage())); + + // verify + verify(memberService).updateMemberInfo(refEq(updateRequest), eq(username)); + } + private Member createMember(String username, String password) { return Member.builder() .username(username) From 25bcfa8fab8fe0b57829185ba74dfc4e8069218a Mon Sep 17 00:00:00 2001 From: dayeong Date: Fri, 7 Feb 2025 18:57:56 +0900 Subject: [PATCH 4/4] =?UTF-8?q?#377=20Refactor:=20memberControllerTest?= =?UTF-8?q?=EC=97=90=20passowrd,=20rePassword=20=EB=A7=A4=EC=B9=AD=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../login/oauth2/controller/OAuth2Controller.java | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/main/java/com/drinkeg/drinkeg/domain/member/login/oauth2/controller/OAuth2Controller.java b/src/main/java/com/drinkeg/drinkeg/domain/member/login/oauth2/controller/OAuth2Controller.java index 9d49c38f..a9ee2a1b 100644 --- a/src/main/java/com/drinkeg/drinkeg/domain/member/login/oauth2/controller/OAuth2Controller.java +++ b/src/main/java/com/drinkeg/drinkeg/domain/member/login/oauth2/controller/OAuth2Controller.java @@ -72,18 +72,5 @@ public ApiResponse deleteApple(@AuthenticationPrincipal PrincipalDetail princ return ApiResponse.onSuccess("애플 회원 탈퇴 성공"); } - @GetMapping("/private-key") - @Operation(summary = "애플 private key Test ", description = "애플 private-key를 확인합니다.") - public String loadPrivateKey() { - try { - // Private Key 가져오기 - PrivateKey privateKey = privateKeyGenerator.getPrivateKey(); - String algorithm = privateKey.getAlgorithm(); - - return "Private key successfully loaded: " + algorithm; - } catch (Exception e) { - return "Failed to load private key: " + e.getMessage(); - } - } }