Skip to content

Commit fd0aa52

Browse files
authored
[Feat] 회원 탈퇴 기능 구현 (#132)
* 회원 탈퇴 기능 구현 * 내 정보 수정 파일 처리 기능 수정
1 parent 44d1361 commit fd0aa52

File tree

4 files changed

+117
-6
lines changed

4 files changed

+117
-6
lines changed

src/main/java/com/backend/domain/member/controller/ApiV1MemberController.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,11 @@ public ResponseEntity<RsData<MemberInfoResponseDto>> memberInfo(@PathVariable Lo
9898
return ResponseEntity.ok(new RsData<>("200", "조회 성공", memberInfo));
9999
}
100100

101-
@Operation(summary = "회원탈퇴 Mock API", description = "회원탈퇴 확인")
101+
@Operation(summary = "회원탈퇴 API", description = "현재 로그인된 회원을 탈퇴 처리합니다.")
102102
@DeleteMapping("/members/me")
103-
public ResponseEntity<RsData<String>> memberWithdraw(Authentication authentication) {
104-
return ResponseEntity.ok(new RsData<>("200-1", "회원 탈퇴가 완료되었습니다.", null));
103+
public ResponseEntity<RsData<Void>> memberWithdraw(Authentication authentication) {
104+
System.out.println("인증이름" + authentication.getName());
105+
RsData<Void> withdrawResult = memberService.withdraw(authentication.getName());
106+
return ResponseEntity.status(withdrawResult.statusCode()).body(withdrawResult);
105107
}
106108
}

src/main/java/com/backend/domain/member/entity/Member.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public class Member extends BaseEntity {
4242

4343
private String profileImageUrl;
4444

45+
@Column(name = "refresh_token", columnDefinition = "TEXT")
4546
private String refreshToken;
4647

4748
private int creditScore;

src/main/java/com/backend/domain/member/service/MemberService.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ public RsData<MemberMyInfoResponseDto> getMyInfo(String email) {
109109
public RsData<MemberMyInfoResponseDto> modify(String email, MemberModifyRequestDto memberModifyRequestDto, MultipartFile profileImage) {
110110
Member member = findMemberByEmail(email);
111111

112-
String profileImageUrl = "";
112+
String profileImageUrl = Optional.ofNullable(member.getProfileImageUrl()).orElse("");
113113
if (profileImage != null && !profileImage.isEmpty()) {
114114
profileImageUrl = fileService.uploadFile(profileImage, "member");
115115
}
@@ -173,4 +173,10 @@ public MemberInfoResponseDto getMemberInfo(Long memberId) {
173173
.orElseThrow(() -> new ServiceException("404", "존재하지 않는 유저입니다."));
174174
return new MemberInfoResponseDto(member);
175175
}
176+
177+
public RsData<Void> withdraw(String email) {
178+
Member member = findMemberByEmail(email);
179+
memberRepository.delete(member);
180+
return new RsData<>("200-5", "회원 탈퇴가 완료되었습니다.", null);
181+
}
176182
}

src/test/java/com/backend/domain/member/controller/ApiV1MemberControllerTest.java

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,13 @@
44
import com.backend.domain.member.dto.MemberModifyRequestDto;
55
import com.backend.domain.member.dto.MemberSignUpRequestDto;
66
import com.backend.domain.member.repository.MemberRepository;
7+
import com.backend.domain.product.service.FileService;
78
import com.backend.global.redis.TestRedisConfiguration;
89
import com.fasterxml.jackson.databind.ObjectMapper;
10+
import com.jayway.jsonpath.JsonPath;
11+
import org.springframework.boot.test.mock.mockito.MockBean;
12+
import static org.mockito.ArgumentMatchers.any;
13+
import static org.mockito.BDDMockito.given;
914
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
1015
import org.springframework.context.annotation.Import;
1116
import org.junit.jupiter.api.AfterEach;
@@ -30,6 +35,7 @@
3035
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
3136
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
3237
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
38+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
3339
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
3440
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
3541

@@ -56,6 +62,9 @@ class ApiV1MemberControllerTest {
5662
@Autowired
5763
private RedisTemplate<String, Object> redisTemplate;
5864

65+
@MockBean
66+
private FileService fileService;
67+
5968
@BeforeEach
6069
void setUp() {
6170
// 각 테스트 실행 전에 데이터 정리
@@ -373,7 +382,9 @@ void t10() throws Exception {
373382
@Test
374383
@DisplayName("내 정보 수정 성공")
375384
void t11() throws Exception {
376-
// Given
385+
// given
386+
given(fileService.uploadFile(any(), any())).willReturn("https://test.com/modified.jpg");
387+
377388
// 회원가입
378389
MemberSignUpRequestDto signUpDto = new MemberSignUpRequestDto(
379390
"[email protected]", "password123", "beforeModify", "01011112222", "Before Address");
@@ -423,7 +434,7 @@ void t11() throws Exception {
423434
.andExpect(jsonPath("$.data.nickname").value("afterModify"))
424435
.andExpect(jsonPath("$.data.phoneNumber").value("01099998888"))
425436
.andExpect(jsonPath("$.data.address").value("After Address"))
426-
.andExpect(jsonPath("$.data.profileImageUrl").exists());
437+
.andExpect(jsonPath("$.data.profileImageUrl").value("https://test.com/modified.jpg"));
427438
}
428439

429440
@Test
@@ -449,4 +460,95 @@ void t12() throws Exception {
449460
.andExpect(jsonPath("$.data.id").value(memberId))
450461
.andExpect(jsonPath("$.data.nickname").value("testUser12"));
451462
}
463+
464+
@Test
465+
@DisplayName("회원탈퇴 성공")
466+
void t13() throws Exception {
467+
// given
468+
// 회원가입
469+
MemberSignUpRequestDto signUpDto = new MemberSignUpRequestDto(
470+
"[email protected]", "password123", "withdrawUser", "01033334444", "Withdraw Address");
471+
mockMvc.perform(post("/api/v1/auth/signup")
472+
.contentType(MediaType.APPLICATION_JSON)
473+
.content(objectMapper.writeValueAsString(signUpDto)));
474+
475+
// 로그인하여 토큰 발급
476+
LoginRequestDto loginDto = new LoginRequestDto("[email protected]", "password123");
477+
ResultActions loginResult = mockMvc.perform(post("/api/v1/auth/login")
478+
.contentType(MediaType.APPLICATION_JSON)
479+
.content(objectMapper.writeValueAsString(loginDto)));
480+
String responseBody = loginResult.andReturn().getResponse().getContentAsString();
481+
String accessToken = objectMapper.readTree(responseBody).get("data").get("accessToken").asText();
482+
483+
// when
484+
// 회원탈퇴
485+
ResultActions withdrawResult = mockMvc.perform(delete("/api/v1/members/me")
486+
.header("Authorization", "Bearer " + accessToken))
487+
.andDo(print());
488+
489+
// then
490+
withdrawResult
491+
.andExpect(status().isOk())
492+
.andExpect(jsonPath("$.resultCode").value("200-5"))
493+
.andExpect(jsonPath("$.msg").value("회원 탈퇴가 완료되었습니다."));
494+
495+
// given
496+
// 탈퇴한 계정으로 다시 로그인 시도
497+
LoginRequestDto reloginDto = new LoginRequestDto("[email protected]", "password123");
498+
499+
// when
500+
ResultActions reloginResult = mockMvc.perform(post("/api/v1/auth/login")
501+
.contentType(MediaType.APPLICATION_JSON)
502+
.content(objectMapper.writeValueAsString(reloginDto)))
503+
.andDo(print());
504+
505+
// then
506+
// 존재하지 않는 이메일이므로 404 Not Found 응답을 기대
507+
reloginResult.andExpect(status().isNotFound());
508+
}
509+
510+
@Test
511+
@DisplayName("내 정보 수정 성공 - 프로필 이미지 제외")
512+
void t14() throws Exception {
513+
// given: FileService가 항상 짧은 URL을 반환하도록 Mocking
514+
given(fileService.uploadFile(any(), any())).willReturn("https://test.com/initial.jpg");
515+
516+
// 1. 초기 이미지와 함께 회원가입 및 정보 수정
517+
MemberSignUpRequestDto signUpDto = new MemberSignUpRequestDto(
518+
"[email protected]", "password123", "imageUser", "01055556666", "Image Address");
519+
mockMvc.perform(post("/api/v1/auth/signup")
520+
.contentType(MediaType.APPLICATION_JSON)
521+
.content(objectMapper.writeValueAsString(signUpDto)));
522+
523+
LoginRequestDto loginDto = new LoginRequestDto("[email protected]", "password123");
524+
ResultActions loginResult = mockMvc.perform(post("/api/v1/auth/login")
525+
.contentType(MediaType.APPLICATION_JSON)
526+
.content(objectMapper.writeValueAsString(loginDto)));
527+
String accessToken = JsonPath.read(loginResult.andReturn().getResponse().getContentAsString(), "$.data.accessToken");
528+
529+
MockMultipartFile initialImage = new MockMultipartFile("profileImage", "initial.jpg", MediaType.IMAGE_JPEG_VALUE, "initial image".getBytes());
530+
MemberModifyRequestDto initialModifyDto = new MemberModifyRequestDto("imageUser", "01055556666", "Image Address");
531+
MockMultipartFile initialModifyDtoPart = new MockMultipartFile("memberModifyRequestDto", "", "application/json", objectMapper.writeValueAsString(initialModifyDto).getBytes(StandardCharsets.UTF_8));
532+
533+
mockMvc.perform(multipart(HttpMethod.PUT, "/api/v1/members/me")
534+
.file(initialImage)
535+
.file(initialModifyDtoPart)
536+
.header("Authorization", "Bearer " + accessToken));
537+
538+
// 2. 이미지를 제외하고 다른 정보만 수정
539+
MemberModifyRequestDto secondModifyDto = new MemberModifyRequestDto("newNickname", "01077778888", "New Address");
540+
MockMultipartFile secondModifyDtoPart = new MockMultipartFile("memberModifyRequestDto", "", "application/json", objectMapper.writeValueAsString(secondModifyDto).getBytes(StandardCharsets.UTF_8));
541+
542+
ResultActions secondModifyResult = mockMvc.perform(multipart(HttpMethod.PUT, "/api/v1/members/me")
543+
.file(secondModifyDtoPart) // 이미지를 보내지 않음
544+
.header("Authorization", "Bearer " + accessToken))
545+
.andDo(print());
546+
547+
// 3. 닉네임은 변경되고, 이미지 URL은 1차 수정 때의 값("https://test.com/initial.jpg")이 그대로 유지되는지 확인
548+
secondModifyResult
549+
.andExpect(status().isOk())
550+
.andExpect(jsonPath("$.resultCode").value("200-4"))
551+
.andExpect(jsonPath("$.data.nickname").value("newNickname"))
552+
.andExpect(jsonPath("$.data.profileImageUrl").value("https://test.com/initial.jpg"));
553+
}
452554
}

0 commit comments

Comments
 (0)