diff --git a/back/src/main/java/com/back/domain/member/member/controller/MemberController.java b/back/src/main/java/com/back/domain/member/member/controller/MemberController.java index c428bb9a..77f03baa 100644 --- a/back/src/main/java/com/back/domain/member/member/controller/MemberController.java +++ b/back/src/main/java/com/back/domain/member/member/controller/MemberController.java @@ -19,7 +19,7 @@ @RequestMapping("/auth") @RequiredArgsConstructor @Tag(name = "MemberController", description = "회원 컨트롤러") -public class MemberController { +public class MemberController { private final MemberService memberService; private final Rq rq; private final EmailVerificationService emailVerificationService; @@ -80,7 +80,7 @@ public RsData login(@RequestBody LoginRequest request) { public RsData logout() { rq.deleteCookie("accessToken"); rq.deleteCookie("refreshToken"); - return new RsData<>("200-1", "로그아웃 성공"); + return new RsData<>("200-8", "로그아웃 성공"); } @GetMapping("/me") @@ -100,4 +100,17 @@ public RsData refresh() { return new RsData<>("200-6", "토큰 갱신 성공"); } + + @DeleteMapping("/me") + @Operation(summary = "회원 탈퇴") + public RsData deleteMember() { + Member currentUser = rq.getActor(); + memberService.deleteMember(currentUser); + + // 탈퇴 후 쿠키 삭제 + rq.deleteCookie("accessToken"); + rq.deleteCookie("refreshToken"); + + return new RsData<>("200-7", "회원 탈퇴가 완료되었습니다."); + } } diff --git a/back/src/main/java/com/back/domain/member/member/service/MemberService.java b/back/src/main/java/com/back/domain/member/member/service/MemberService.java index 34e05f71..a8d2d620 100644 --- a/back/src/main/java/com/back/domain/member/member/service/MemberService.java +++ b/back/src/main/java/com/back/domain/member/member/service/MemberService.java @@ -92,6 +92,22 @@ public boolean isValidToken(String token) { return authTokenService.isValidToken(token); } + @Transactional + public void deleteMember(Member currentUser) { + if (currentUser == null) { + throw new ServiceException("401-1", "로그인이 필요합니다."); + } + + Member member = memberRepository.findById(currentUser.getId()) + .orElseThrow(() -> new ServiceException("404-1", "존재하지 않는 회원입니다.")); + + // 관련 엔티티들 먼저 삭제 + menteeRepository.findByMemberId(member.getId()).ifPresent(menteeRepository::delete); + mentorRepository.findByMemberId(member.getId()).ifPresent(mentorRepository::delete); + + memberRepository.delete(member); + } + public boolean isRefreshToken(String token) { return authTokenService.isRefreshToken(token); } diff --git a/back/src/test/java/com/back/domain/member/member/controller/MemberControllerTest.java b/back/src/test/java/com/back/domain/member/member/controller/MemberControllerTest.java index ed25cf63..544dfdba 100644 --- a/back/src/test/java/com/back/domain/member/member/controller/MemberControllerTest.java +++ b/back/src/test/java/com/back/domain/member/member/controller/MemberControllerTest.java @@ -16,6 +16,7 @@ import org.springframework.test.web.servlet.ResultActions; import org.springframework.transaction.annotation.Transactional; +import static org.assertj.core.api.Assertions.assertThat; 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.*; @@ -368,7 +369,7 @@ void t9() throws Exception { result .andExpect(status().is2xxSuccessful()) - .andExpect(jsonPath("$.resultCode").value("200-1")) + .andExpect(jsonPath("$.resultCode").value("200-8")) .andExpect(jsonPath("$.msg").value("로그아웃 성공")) .andExpect(cookie().maxAge("accessToken", 0)) .andExpect(cookie().maxAge("refreshToken", 0)); @@ -443,4 +444,56 @@ void t11() throws Exception { .andExpect(jsonPath("$.msg").value("이미 존재하는 닉네임입니다.")); } + @Test + @DisplayName("회원 탈퇴 성공") + void t12() throws Exception { + // 멘티 회원가입 + String email = "delete@example.com"; + memberService.joinMentee(email, "탈퇴사용자", "탈퇴닉네임", "password123", "Backend"); + + // 로그인하여 쿠키 받기 + ResultActions loginResult = mvc.perform( + post("/auth/login") + .contentType(MediaType.APPLICATION_JSON) + .content(String.format(""" + { + "email": "%s", + "password": "password123" + } + """, email)) + ); + + Cookie accessToken = loginResult.andReturn().getResponse().getCookie("accessToken"); + + // 회원 탈퇴 요청 + ResultActions result = mvc + .perform( + delete("/auth/me") + .cookie(accessToken) + ) + .andDo(print()); + + result + .andExpect(status().is2xxSuccessful()) + .andExpect(jsonPath("$.resultCode").value("200-7")) + .andExpect(jsonPath("$.msg").value("회원 탈퇴가 완료되었습니다.")) + .andExpect(cookie().maxAge("accessToken", 0)) + .andExpect(cookie().maxAge("refreshToken", 0)); + + // 탈퇴 후 해당 이메일로 조회했을 때 없어야 함 + assertThat(memberService.findByEmail(email)).isEmpty(); + } + + @Test + @DisplayName("로그인하지 않은 상태에서 회원 탈퇴 시도 - 실패") + void t13() throws Exception { + // 로그인 없이 회원 탈퇴 시도 + ResultActions result = mvc + .perform(delete("/auth/me")) + .andDo(print()); + + result + .andExpect(status().isUnauthorized()); + } + }