diff --git a/back/src/main/java/com/back/domain/member/member/controller/AdmMemberController.java b/back/src/main/java/com/back/domain/member/member/controller/AdmMemberController.java new file mode 100644 index 00000000..be76a1e0 --- /dev/null +++ b/back/src/main/java/com/back/domain/member/member/controller/AdmMemberController.java @@ -0,0 +1,57 @@ +package com.back.domain.member.member.controller; + +import com.back.domain.member.member.dto.MemberSearchResponse; +import com.back.domain.member.member.dto.MentorUpdateRequest; +import com.back.domain.member.member.dto.MenteeUpdateRequest; +import com.back.domain.member.member.service.MemberService; +import com.back.global.rq.Rq; +import com.back.global.rsData.RsData; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/members") +@RequiredArgsConstructor +@Tag(name = "AdmMemberController", description = "관리자 회원 관리 컨트롤러") +public class AdmMemberController { + private final MemberService memberService; + private final Rq rq; + + + @GetMapping("/{memberId}") + @Operation(summary = "회원 상세 조회 (관리자)") + @PreAuthorize("hasRole('ADMIN')") + public RsData getMember(@PathVariable Long memberId) { + MemberSearchResponse member = memberService.getMemberForAdmin(memberId); + return new RsData<>("200-14", "회원 상세 조회 성공", member); + } + + @PostMapping("/{memberId}/delete") + @Operation(summary = "회원 삭제 (관리자) - 소프트 삭제") + @PreAuthorize("hasRole('ADMIN')") + public RsData deleteMember(@PathVariable Long memberId) { + memberService.deleteMemberByAdmin(memberId); + return new RsData<>("200-15", "회원 삭제 성공"); + } + + @PutMapping("/{memberId}/mentor") + @Operation(summary = "멘토 정보 수정 (관리자)") + @PreAuthorize("hasRole('ADMIN')") + public RsData updateMentor(@PathVariable Long memberId, @RequestBody MentorUpdateRequest request) { + memberService.updateMemberByAdmin(memberId, null, request.nickname(), null, + request.career(), request.careerYears(), null); + return new RsData<>("200-16", "멘토 정보 수정 성공"); + } + + @PutMapping("/{memberId}/mentee") + @Operation(summary = "멘티 정보 수정 (관리자)") + @PreAuthorize("hasRole('ADMIN')") + public RsData updateMentee(@PathVariable Long memberId, @RequestBody MenteeUpdateRequest request) { + memberService.updateMemberByAdmin(memberId, null, request.nickname(), null, + null, null, request.interestedField()); + return new RsData<>("200-17", "멘티 정보 수정 성공"); + } +} \ No newline at end of file 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/MemberAuthController.java similarity index 71% rename from back/src/main/java/com/back/domain/member/member/controller/MemberController.java rename to back/src/main/java/com/back/domain/member/member/controller/MemberAuthController.java index 08a18c7b..5827ee62 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/MemberAuthController.java @@ -1,6 +1,9 @@ package com.back.domain.member.member.controller; -import com.back.domain.member.member.dto.*; +import com.back.domain.member.member.dto.MenteeSignupRequest; +import com.back.domain.member.member.dto.MentorSignupVerifyRequest; +import com.back.domain.member.member.dto.MentorVerificationRequest; +import com.back.domain.member.member.dto.LoginRequest; import com.back.domain.member.member.entity.Member; import com.back.domain.member.member.service.MemberService; import com.back.global.rq.Rq; @@ -15,8 +18,8 @@ @RestController @RequestMapping("/auth") @RequiredArgsConstructor -@Tag(name = "MemberController", description = "회원 컨트롤러") -public class MemberController { +@Tag(name = "MemberAuthController", description = "회원 인증 컨트롤러") +public class MemberAuthController { private final MemberService memberService; private final Rq rq; private final EmailVerificationService emailVerificationService; @@ -102,9 +105,9 @@ public RsData refresh() { return new RsData<>("200-6", "토큰 갱신 성공"); } - @DeleteMapping("/me") + @PostMapping("/me/withdraw") @Operation(summary = "회원 탈퇴") - public RsData deleteMember() { + public RsData withdrawMember() { Member currentUser = rq.getActor(); memberService.deleteMember(currentUser); @@ -115,35 +118,4 @@ public RsData deleteMember() { return new RsData<>("200-7", "회원 탈퇴가 완료되었습니다."); } - @GetMapping("/me/mentee") - @Operation(summary = "멘티 마이페이지 조회") - public RsData getMenteeMyPage() { - Member currentUser = rq.getActor(); - MenteeMyPageResponse response = memberService.getMenteeMyPage(currentUser); - return new RsData<>("200-9", "멘티 정보 조회 성공", response); - } - - @PutMapping("/me/mentee") - @Operation(summary = "멘티 정보 수정") - public RsData updateMentee(@RequestBody MenteeUpdateRequest request) { - Member currentUser = rq.getActor(); - memberService.updateMentee(currentUser, request); - return new RsData<>("200-10", "멘티 정보 수정 성공"); - } - - @GetMapping("/me/mentor") - @Operation(summary = "멘토 마이페이지 조회") - public RsData getMentorMyPage() { - Member currentUser = rq.getActor(); - MentorMyPageResponse response = memberService.getMentorMyPage(currentUser); - return new RsData<>("200-11", "멘토 정보 조회 성공", response); - } - - @PutMapping("/me/mentor") - @Operation(summary = "멘토 정보 수정") - public RsData updateMentor(@RequestBody MentorUpdateRequest request) { - Member currentUser = rq.getActor(); - memberService.updateMentor(currentUser, request); - return new RsData<>("200-12", "멘토 정보 수정 성공"); - } } diff --git a/back/src/main/java/com/back/domain/member/member/controller/MemberMyPageController.java b/back/src/main/java/com/back/domain/member/member/controller/MemberMyPageController.java new file mode 100644 index 00000000..725d4309 --- /dev/null +++ b/back/src/main/java/com/back/domain/member/member/controller/MemberMyPageController.java @@ -0,0 +1,55 @@ +package com.back.domain.member.member.controller; + +import com.back.domain.member.member.dto.MenteeMyPageResponse; +import com.back.domain.member.member.dto.MenteeUpdateRequest; +import com.back.domain.member.member.dto.MentorMyPageResponse; +import com.back.domain.member.member.dto.MentorUpdateRequest; +import com.back.domain.member.member.entity.Member; +import com.back.domain.member.member.service.MemberService; +import com.back.global.rq.Rq; +import com.back.global.rsData.RsData; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/members") +@RequiredArgsConstructor +@Tag(name = "MemberMyPageController", description = "회원 마이페이지 컨트롤러") +public class MemberMyPageController { + private final MemberService memberService; + private final Rq rq; + + @GetMapping("/me/mentee") + @Operation(summary = "멘티 마이페이지 조회") + public RsData getMenteeMyPage() { + Member currentUser = rq.getActor(); + MenteeMyPageResponse response = memberService.getMenteeMyPage(currentUser); + return new RsData<>("200-9", "멘티 정보 조회 성공", response); + } + + @PutMapping("/me/mentee") + @Operation(summary = "멘티 정보 수정") + public RsData updateMentee(@RequestBody MenteeUpdateRequest request) { + Member currentUser = rq.getActor(); + memberService.updateMentee(currentUser, request); + return new RsData<>("200-10", "멘티 정보 수정 성공"); + } + + @GetMapping("/me/mentor") + @Operation(summary = "멘토 마이페이지 조회") + public RsData getMentorMyPage() { + Member currentUser = rq.getActor(); + MentorMyPageResponse response = memberService.getMentorMyPage(currentUser); + return new RsData<>("200-11", "멘토 정보 조회 성공", response); + } + + @PutMapping("/me/mentor") + @Operation(summary = "멘토 정보 수정") + public RsData updateMentor(@RequestBody MentorUpdateRequest request) { + Member currentUser = rq.getActor(); + memberService.updateMentor(currentUser, request); + return new RsData<>("200-12", "멘토 정보 수정 성공"); + } +} \ No newline at end of file diff --git a/back/src/main/java/com/back/domain/member/member/dto/MemberSearchResponse.java b/back/src/main/java/com/back/domain/member/member/dto/MemberSearchResponse.java new file mode 100644 index 00000000..0d64aa4f --- /dev/null +++ b/back/src/main/java/com/back/domain/member/member/dto/MemberSearchResponse.java @@ -0,0 +1,37 @@ +package com.back.domain.member.member.dto; + +import com.back.domain.member.member.entity.Member; +import com.back.domain.member.mentee.entity.Mentee; +import com.back.domain.member.mentor.entity.Mentor; + +import java.time.LocalDateTime; + +public record MemberSearchResponse( + Long id, + String email, + String name, + String nickname, + Member.Role role, + Boolean isDeleted, + LocalDateTime createdAt, + LocalDateTime modifiedAt, + String career, // 멘토인 경우에만 값이 있음 (TODO: Job 연결 후 수정 예정) + Integer careerYears, // 멘토인 경우에만 값이 있음 + String interestedField // 멘티인 경우에만 값이 있음 (TODO: Job 연결 후 수정 예정) +) { + public static MemberSearchResponse from(Member member, Mentor mentor, Mentee mentee) { + return new MemberSearchResponse( + member.getId(), + member.getEmail(), + member.getName(), + member.getNickname(), + member.getRole(), + member.getIsDeleted(), + member.getCreateDate(), + member.getModifyDate(), + mentor != null ? "TODO: Job 연결 필요" : null, // TODO: Job 연결 후 수정 + mentor != null ? mentor.getCareerYears() : null, + mentee != null ? "TODO: Job 연결 필요" : null // TODO: Job 연결 후 수정 + ); + } +} \ No newline at end of file diff --git a/back/src/main/java/com/back/domain/member/member/entity/Member.java b/back/src/main/java/com/back/domain/member/member/entity/Member.java index 781d9255..c0d5c710 100644 --- a/back/src/main/java/com/back/domain/member/member/entity/Member.java +++ b/back/src/main/java/com/back/domain/member/member/entity/Member.java @@ -14,13 +14,13 @@ public class Member extends BaseEntity { @Column(unique = true, nullable = false, length = 36) private String publicId; - @Column(unique = true, nullable = false) + @Column(nullable = false) private String email; @Column(nullable = false) private String name; - @Column(unique = true, nullable = false) + @Column(nullable = false) private String nickname; @Column(nullable = false) @@ -30,6 +30,9 @@ public class Member extends BaseEntity { @Column(nullable = false) private Role role; + @Column(nullable = false) + private Boolean isDeleted = false; + public enum Role { MENTOR, MENTEE, ADMIN } @@ -38,12 +41,21 @@ public void updateNickname(String nickname) { this.nickname = nickname; } + public void updateName(String name) { + this.name = name; + } + + public void updateEmail(String email) { + this.email = email; + } + public Member(String email, String password, String name, String nickname, Role role) { this.email = email; this.password = password; this.name = name; this.nickname = nickname; this.role = role; + this.isDeleted = false; } public Member(Long id, String email, String name, String nickname, Role role) { @@ -52,6 +64,11 @@ public Member(Long id, String email, String name, String nickname, Role role) { this.name = name; this.nickname = nickname; this.role = role; + this.isDeleted = false; + } + + public void delete() { + this.isDeleted = true; } @PrePersist diff --git a/back/src/main/java/com/back/domain/member/member/repository/MemberRepository.java b/back/src/main/java/com/back/domain/member/member/repository/MemberRepository.java index 3d8fef24..5d2360e3 100644 --- a/back/src/main/java/com/back/domain/member/member/repository/MemberRepository.java +++ b/back/src/main/java/com/back/domain/member/member/repository/MemberRepository.java @@ -2,10 +2,29 @@ import com.back.domain.member.member.entity.Member; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import java.util.Optional; public interface MemberRepository extends JpaRepository { - Optional findByEmail(String email); - Optional findByNickname(String nickname); + // 활성 사용자만 조회 (소프트 삭제된 사용자 제외) + @Query("SELECT m FROM Member m WHERE m.email = :email AND m.isDeleted = false") + Optional findByEmail(@Param("email") String email); + + @Query("SELECT m FROM Member m WHERE m.nickname = :nickname AND m.isDeleted = false") + Optional findByNickname(@Param("nickname") String nickname); + + @Query("SELECT m FROM Member m WHERE m.id = :id AND m.isDeleted = false") + Optional findById(@Param("id") Long id); + + // 관리자용 + @Query("SELECT m FROM Member m WHERE m.email = :email") + Optional findByEmailIncludingDeleted(@Param("email") String email); + + @Query("SELECT m FROM Member m WHERE m.nickname = :nickname") + Optional findByNicknameIncludingDeleted(@Param("nickname") String nickname); + + @Query("SELECT m FROM Member m WHERE m.id = :id") + Optional findByIdIncludingDeleted(@Param("id") Long id); } 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 1ac4f20d..d25d195c 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 @@ -1,9 +1,6 @@ package com.back.domain.member.member.service; -import com.back.domain.member.member.dto.MenteeMyPageResponse; -import com.back.domain.member.member.dto.MenteeUpdateRequest; -import com.back.domain.member.member.dto.MentorMyPageResponse; -import com.back.domain.member.member.dto.MentorUpdateRequest; +import com.back.domain.member.member.dto.*; import com.back.domain.member.member.entity.Member; import com.back.domain.member.member.repository.MemberRepository; import com.back.domain.member.mentee.entity.Mentee; @@ -30,12 +27,14 @@ public class MemberService { @Transactional public Member joinMentee(String email, String name, String nickname, String password, String interestedField) { + // 활성 사용자 중 이메일 중복 체크 (탈퇴한 사용자 제외) memberRepository.findByEmail(email).ifPresent( member -> { throw new ServiceException("400-1", "이미 존재하는 이메일입니다."); } ); + // 활성 사용자 중 닉네임 중복 체크 (탈퇴한 사용자 제외) memberRepository.findByNickname(nickname).ifPresent( member -> { throw new ServiceException("400-3", "이미 존재하는 닉네임입니다."); @@ -54,12 +53,14 @@ public Member joinMentee(String email, String name, String nickname, String pass @Transactional public Member joinMentor(String email, String name, String nickname, String password, String career, Integer careerYears) { + // 활성 사용자 중 이메일 중복 체크 (탈퇴한 사용자 제외) memberRepository.findByEmail(email).ifPresent( member -> { throw new ServiceException("400-2", "이미 존재하는 이메일입니다."); } ); + // 활성 사용자 중 닉네임 중복 체크 (탈퇴한 사용자 제외) memberRepository.findByNickname(nickname).ifPresent( member -> { throw new ServiceException("400-4", "이미 존재하는 닉네임입니다."); @@ -105,11 +106,20 @@ public void deleteMember(Member currentUser) { 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); + // 소프트 삭제 처리 + member.delete(); + memberRepository.save(member); + + // 관련 엔티티들도 소프트 삭제 + menteeRepository.findByMemberIdIncludingDeleted(member.getId()).ifPresent(mentee -> { + mentee.delete(); + menteeRepository.save(mentee); + }); - memberRepository.delete(member); + mentorRepository.findByMemberIdIncludingDeleted(member.getId()).ifPresent(mentor -> { + mentor.delete(); + mentorRepository.save(mentor); + }); } public boolean isRefreshToken(String token) { @@ -171,16 +181,8 @@ public MenteeMyPageResponse getMenteeMyPage(Member currentUser) { @Transactional public void updateMentee(Member currentUser, MenteeUpdateRequest request) { - // 닉네임 중복 체크 (본인 제외) - if (!currentUser.getNickname().equals(request.nickname())) { - memberRepository.findByNickname(request.nickname()).ifPresent( - member -> { - if (!member.getId().equals(currentUser.getId())) { - throw new ServiceException("400-3", "이미 존재하는 닉네임입니다."); - } - } - ); - } + // 닉네임 중복 체크 + validateNicknameDuplicate(request.nickname(), currentUser); // Member 정보 업데이트 (닉네임) Member member = memberRepository.findById(currentUser.getId()) @@ -201,16 +203,8 @@ public MentorMyPageResponse getMentorMyPage(Member currentUser) { @Transactional public void updateMentor(Member currentUser, MentorUpdateRequest request) { - // 닉네임 중복 체크 (본인 제외) - if (!currentUser.getNickname().equals(request.nickname())) { - memberRepository.findByNickname(request.nickname()).ifPresent( - member -> { - if (!member.getId().equals(currentUser.getId())) { - throw new ServiceException("400-4", "이미 존재하는 닉네임입니다."); - } - } - ); - } + // 닉네임 중복 체크 + validateNicknameDuplicate(request.nickname(), currentUser); Mentor mentor = mentorRepository.findByMemberId(currentUser.getId()) .orElseThrow(() -> new ServiceException("404-3", "멘토 정보를 찾을 수 없습니다.")); @@ -228,4 +222,94 @@ public void updateMentor(Member currentUser, MentorUpdateRequest request) { // TODO: career를 jobId로 매핑하는 로직 필요 (현재는 기존 jobId 유지) } + + + public MemberSearchResponse getMemberForAdmin(Long memberId) { + Member member = memberRepository.findByIdIncludingDeleted(memberId) + .orElseThrow(() -> new ServiceException("404-1", "존재하지 않는 회원입니다.")); + + Mentor mentor = null; + Mentee mentee = null; + + if (member.getRole() == Member.Role.MENTOR) { + mentor = mentorRepository.findByMemberIdIncludingDeleted(member.getId()).orElse(null); + } else if (member.getRole() == Member.Role.MENTEE) { + mentee = menteeRepository.findByMemberIdIncludingDeleted(member.getId()).orElse(null); + } + + return MemberSearchResponse.from(member, mentor, mentee); + } + + @Transactional + public void deleteMemberByAdmin(Long memberId) { + Member member = memberRepository.findByIdIncludingDeleted(memberId) + .orElseThrow(() -> new ServiceException("404-1", "존재하지 않는 회원입니다.")); + + // 이미 탈퇴한 회원인지 확인 + if (member.getIsDeleted()) { + throw new ServiceException("400-1", "이미 탈퇴한 회원입니다."); + } + + // 소프트 삭제 처리 + member.delete(); + memberRepository.save(member); + + // 관련 엔티티들도 소프트 삭제 + menteeRepository.findByMemberIdIncludingDeleted(member.getId()).ifPresent(mentee -> { + mentee.delete(); + menteeRepository.save(mentee); + }); + + mentorRepository.findByMemberIdIncludingDeleted(member.getId()).ifPresent(mentor -> { + mentor.delete(); + mentorRepository.save(mentor); + }); + } + + @Transactional + public void updateMemberByAdmin(Long memberId, String name, String nickname, String email, + String career, Integer careerYears, String interestedField) { + Member member = memberRepository.findByIdIncludingDeleted(memberId) + .orElseThrow(() -> new ServiceException("404-1", "존재하지 않는 회원입니다.")); + + // 중복 체크 + validateEmailDuplicate(email, member); + validateNicknameDuplicate(nickname, member); + + // Member 정보 업데이트 + if (name != null) member.updateName(name); + if (nickname != null) member.updateNickname(nickname); + if (email != null) member.updateEmail(email); + + memberRepository.save(member); + + // 역할별 추가 정보 업데이트 + if (member.getRole() == Member.Role.MENTOR && (career != null || careerYears != null)) { + Mentor mentor = mentorRepository.findByMemberIdIncludingDeleted(member.getId()) + .orElse(null); + if (mentor != null) { + if (careerYears != null) mentor.updateCareerYears(careerYears); + mentorRepository.save(mentor); + } + } else if (member.getRole() == Member.Role.MENTEE && interestedField != null) { + // TODO: interestedField 업데이트 로직 필요 (Mentee 엔티티에 업데이트 메서드가 있을 때) + } + } + + private void validateEmailDuplicate(String email, Member currentMember) { + if (email == null || email.equals(currentMember.getEmail())) return; + + if (memberRepository.findByEmail(email).isPresent()) { + throw new ServiceException("400-1", "이미 존재하는 이메일입니다."); + } + } + + private void validateNicknameDuplicate(String nickname, Member currentMember) { + if (nickname == null || nickname.equals(currentMember.getNickname())) return; + + if (memberRepository.findByNickname(nickname).isPresent()) { + throw new ServiceException("400-3", "이미 존재하는 닉네임입니다."); + } + } + } diff --git a/back/src/main/java/com/back/domain/member/mentee/entity/Mentee.java b/back/src/main/java/com/back/domain/member/mentee/entity/Mentee.java index d72b068c..64b65bc5 100644 --- a/back/src/main/java/com/back/domain/member/mentee/entity/Mentee.java +++ b/back/src/main/java/com/back/domain/member/mentee/entity/Mentee.java @@ -18,9 +18,17 @@ public class Mentee extends BaseEntity { @Column(name = "job_id") private Long jobId; + @Column(nullable = false) + private Boolean isDeleted = false; + @Builder public Mentee(Member member, Long jobId) { this.member = member; this.jobId = jobId; + this.isDeleted = false; + } + + public void delete() { + this.isDeleted = true; } } diff --git a/back/src/main/java/com/back/domain/member/mentee/repository/MenteeRepository.java b/back/src/main/java/com/back/domain/member/mentee/repository/MenteeRepository.java index b66d56ed..4fece262 100644 --- a/back/src/main/java/com/back/domain/member/mentee/repository/MenteeRepository.java +++ b/back/src/main/java/com/back/domain/member/mentee/repository/MenteeRepository.java @@ -2,9 +2,20 @@ import com.back.domain.member.mentee.entity.Mentee; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import java.util.Optional; public interface MenteeRepository extends JpaRepository { - Optional findByMemberId(Long memberId); + // 활성 멘티만 조회 (소프트 삭제된 멘티 제외) + @Query("SELECT m FROM Mentee m WHERE m.member.id = :memberId AND m.isDeleted = false") + Optional findByMemberId(@Param("memberId") Long memberId); + + @Query("SELECT m FROM Mentee m WHERE m.id = :id AND m.isDeleted = false") + Optional findById(@Param("id") Long id); + + // 삭제된 멘티 포함 조회 (관리자용) + @Query("SELECT m FROM Mentee m WHERE m.member.id = :memberId") + Optional findByMemberIdIncludingDeleted(@Param("memberId") Long memberId); } \ No newline at end of file diff --git a/back/src/main/java/com/back/domain/member/mentor/entity/Mentor.java b/back/src/main/java/com/back/domain/member/mentor/entity/Mentor.java index a86b8fe1..fe961834 100644 --- a/back/src/main/java/com/back/domain/member/mentor/entity/Mentor.java +++ b/back/src/main/java/com/back/domain/member/mentor/entity/Mentor.java @@ -24,15 +24,23 @@ public class Mentor extends BaseEntity { @Column(name = "career_years") private Integer careerYears; + @Column(nullable = false) + private Boolean isDeleted = false; + @Builder public Mentor(Member member, Long jobId, Double rate, Integer careerYears) { this.member = member; this.jobId = jobId; this.rate = rate; this.careerYears = careerYears; + this.isDeleted = false; } public void updateCareerYears(Integer careerYears) { this.careerYears = careerYears; } + + public void delete() { + this.isDeleted = true; + } } diff --git a/back/src/main/java/com/back/domain/member/mentor/repository/MentorRepository.java b/back/src/main/java/com/back/domain/member/mentor/repository/MentorRepository.java index fa31fc97..14dcfeaf 100644 --- a/back/src/main/java/com/back/domain/member/mentor/repository/MentorRepository.java +++ b/back/src/main/java/com/back/domain/member/mentor/repository/MentorRepository.java @@ -2,9 +2,20 @@ import com.back.domain.member.mentor.entity.Mentor; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import java.util.Optional; public interface MentorRepository extends JpaRepository { - Optional findByMemberId(Long memberId); + // 활성 멘토만 조회 (소프트 삭제된 멘토 제외) + @Query("SELECT m FROM Mentor m WHERE m.member.id = :memberId AND m.isDeleted = false") + Optional findByMemberId(@Param("memberId") Long memberId); + + @Query("SELECT m FROM Mentor m WHERE m.id = :id AND m.isDeleted = false") + Optional findById(@Param("id") Long id); + + // 삭제된 멘토 포함 조회 (관리자용) + @Query("SELECT m FROM Mentor m WHERE m.member.id = :memberId") + Optional findByMemberIdIncludingDeleted(@Param("memberId") Long memberId); } \ No newline at end of file diff --git a/back/src/test/java/com/back/domain/member/member/controller/AdminMemberControllerTest.java b/back/src/test/java/com/back/domain/member/member/controller/AdminMemberControllerTest.java new file mode 100644 index 00000000..403bd41c --- /dev/null +++ b/back/src/test/java/com/back/domain/member/member/controller/AdminMemberControllerTest.java @@ -0,0 +1,296 @@ +package com.back.domain.member.member.controller; + +import com.back.domain.member.member.entity.Member; +import com.back.domain.member.member.service.MemberService; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.http.MediaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.back.domain.member.member.dto.MentorUpdateRequest; +import com.back.domain.member.member.dto.MenteeUpdateRequest; + +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.*; +import static org.junit.jupiter.api.Assertions.*; + +@ActiveProfiles("test") +@SpringBootTest +@AutoConfigureMockMvc +@Transactional +public class AdminMemberControllerTest { + @Autowired + private MemberService memberService; + + @Autowired + private MockMvc mvc; + + @Autowired + private ObjectMapper objectMapper; + + @Test + @DisplayName("관리자 회원 상세 조회 성공") + @WithMockUser(roles = "ADMIN") + void t1() throws Exception { + // 멘티 회원가입 + String email = "mentee@example.com"; + Member mentee = memberService.joinMentee(email, "멘티유저", "멘티닉네임", "password123", "Backend"); + + // 관리자 권한으로 회원 정보 조회 + ResultActions result = mvc + .perform(get("/members/" + mentee.getId())) + .andDo(print()); + + result + .andExpect(handler().handlerType(AdmMemberController.class)) + .andExpect(handler().methodName("getMember")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.resultCode").value("200-14")) + .andExpect(jsonPath("$.msg").value("회원 상세 조회 성공")) + .andExpect(jsonPath("$.data.email").value(email)) + .andExpect(jsonPath("$.data.role").value("MENTEE")); + } + + @Test + @DisplayName("관리자가 아닌 사용자의 회원 정보 조회 - 실패") + @WithMockUser(roles = "MENTEE") + void t2() throws Exception { + // 테스트용 회원 생성 + String email = "test@example.com"; + Member testMember = memberService.joinMentee(email, "테스트유저", "테스트닉네임", "password123", "Backend"); + + // 일반 사용자(MENTEE 권한)가 관리자 기능 접근 시도 + ResultActions result = mvc + .perform(get("/members/" + testMember.getId())) + .andDo(print()); + + result.andExpect(status().isForbidden()); // 403 Forbidden 예상 + } + + @Test + @DisplayName("로그인하지 않은 상태에서 관리자 기능 접근 - 실패") + void t3() throws Exception { + Long memberId = 1L; + + ResultActions result = mvc + .perform(get("/members/" + memberId)) + .andDo(print()); + + result + .andExpect(status().is4xxClientError()); + } + + @Test + @DisplayName("멘티 검색 시 희망직업 정보 확인") + @WithMockUser(roles = "ADMIN") + void t4() throws Exception { + // 멘티 회원가입 + String email = "mentee@test.com"; + Member mentee = memberService.joinMentee(email, "멘티유저", "멘티닉네임", "password123", "Backend"); + + // 관리자가 멘티 정보 조회 + ResultActions result = mvc + .perform(get("/members/" + mentee.getId())) + .andDo(print()); + + result + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.role").value("MENTEE")) + .andExpect(jsonPath("$.data.interestedField").exists()) // 희망직업 필드 존재 확인 + .andExpect(jsonPath("$.data.careerYears").doesNotExist()); // 멘티는 연차 없음 + } + + @Test + @DisplayName("멘토 검색 시 직업과 연차 정보 확인") + @WithMockUser(roles = "ADMIN") + void t5() throws Exception { + // 멘토 회원가입 + String email = "mentor@test.com"; + Member mentor = memberService.joinMentor(email, "멘토유저", "멘토닉네임", "password123", "Backend", 5); + + // 관리자가 멘토 정보 조회 + ResultActions result = mvc + .perform(get("/members/" + mentor.getId())) + .andDo(print()); + + result + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.role").value("MENTOR")) + .andExpect(jsonPath("$.data.career").exists()) // 직업 필드 존재 확인 + .andExpect(jsonPath("$.data.careerYears").value(5)) // 연차 확인 + .andExpect(jsonPath("$.data.interestedField").doesNotExist()); // 멘토는 희망직업 없음 + } + + @Test + @DisplayName("관리자 회원 삭제 성공") + @WithMockUser(roles = "ADMIN") + void t6() throws Exception { + // 테스트용 멘티 생성 + String email = "test@delete.com"; + Member member = memberService.joinMentee(email, "삭제될유저", "삭제될닉네임", "password123", "Backend"); + + // 관리자가 회원 삭제 + ResultActions result = mvc + .perform(post("/members/" + member.getId() + "/delete")) + .andDo(print()); + + result + .andExpect(handler().handlerType(AdmMemberController.class)) + .andExpect(handler().methodName("deleteMember")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.resultCode").value("200-15")) + .andExpect(jsonPath("$.msg").value("회원 삭제 성공")); + } + + @Test + @DisplayName("관리자가 아닌 사용자의 회원 삭제 시도 - 실패") + @WithMockUser(roles = "MENTEE") + void t7() throws Exception { + // 테스트용 멘티 생성 + String email = "test@fail.com"; + Member member = memberService.joinMentee(email, "테스트유저", "테스트닉네임", "password123", "Backend"); + + // 일반 사용자가 회원 삭제 시도 + ResultActions result = mvc + .perform(post("/members/" + member.getId() + "/delete")) + .andDo(print()); + + result.andExpect(status().isForbidden()); // 403 Forbidden 예상 + } + + @Test + @DisplayName("관리자 멘토 정보 수정 성공") + @WithMockUser(roles = "ADMIN") + void t8() throws Exception { + // 테스트용 멘토 생성 + String email = "mentor@update.com"; + Member mentor = memberService.joinMentor(email, "멘토유저", "멘토닉네임", "password123", "Backend", 3); + + // 수정할 데이터 + MentorUpdateRequest updateRequest = new MentorUpdateRequest("새로운멘토닉네임", "Frontend", 5); + + // 관리자가 멘토 정보 수정 + ResultActions result = mvc + .perform(put("/members/" + mentor.getId() + "/mentor") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(updateRequest))) + .andDo(print()); + + result + .andExpect(handler().handlerType(AdmMemberController.class)) + .andExpect(handler().methodName("updateMentor")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.resultCode").value("200-16")) + .andExpect(jsonPath("$.msg").value("멘토 정보 수정 성공")); + + // 수정 후 실제로 값이 변경되었는지 확인 + ResultActions verifyResult = mvc + .perform(get("/members/" + mentor.getId())) + .andDo(print()); + + verifyResult + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.nickname").value("새로운멘토닉네임")) + .andExpect(jsonPath("$.data.careerYears").value(5)); + } + + @Test + @DisplayName("관리자 멘티 정보 수정 성공") + @WithMockUser(roles = "ADMIN") + void t9() throws Exception { + // 테스트용 멘티 생성 + String email = "mentee@update.com"; + Member mentee = memberService.joinMentee(email, "멘티유저", "멘티닉네임", "password123", "Backend"); + + // 수정할 데이터 + MenteeUpdateRequest updateRequest = new MenteeUpdateRequest("새로운멘티닉네임", "AI/ML"); + + // 관리자가 멘티 정보 수정 + ResultActions result = mvc + .perform(put("/members/" + mentee.getId() + "/mentee") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(updateRequest))) + .andDo(print()); + + result + .andExpect(handler().handlerType(AdmMemberController.class)) + .andExpect(handler().methodName("updateMentee")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.resultCode").value("200-17")) + .andExpect(jsonPath("$.msg").value("멘티 정보 수정 성공")); + + // 수정 후 실제로 값이 변경되었는지 확인 + ResultActions verifyResult = mvc + .perform(get("/members/" + mentee.getId())) + .andDo(print()); + + verifyResult + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.nickname").value("새로운멘티닉네임")); + } + + @Test + @DisplayName("관리자가 아닌 사용자의 멘토 정보 수정 시도 - 실패") + @WithMockUser(roles = "MENTEE") + void t10() throws Exception { + // 테스트용 멘토 생성 + String email = "mentor@fail.com"; + Member mentor = memberService.joinMentor(email, "멘토유저", "멘토닉네임", "password123", "Backend", 3); + + MentorUpdateRequest updateRequest = new MentorUpdateRequest("해킹시도닉네임", "Hacker", 10); + + // 일반 사용자가 멘토 정보 수정 시도 + ResultActions result = mvc + .perform(put("/members/" + mentor.getId() + "/mentor") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(updateRequest))) + .andDo(print()); + + result.andExpect(status().isForbidden()); // 403 Forbidden 예상 + } + + @Test + @DisplayName("존재하지 않는 회원 조회 - 실패") + @WithMockUser(roles = "ADMIN") + void t11() throws Exception { + Long nonExistentId = 999999L; + + ResultActions result = mvc + .perform(get("/members/" + nonExistentId)) + .andDo(print()); + + result.andExpect(status().is4xxClientError()); + } + + @Test + @DisplayName("닉네임 중복으로 멘토 수정 실패") + @WithMockUser(roles = "ADMIN") + void t12() throws Exception { + // 첫 번째 멘토 생성 + String email1 = "mentor1@test.com"; + Member mentor1 = memberService.joinMentor(email1, "멘토1", "기존닉네임", "password123", "Backend", 3); + + // 두 번째 멘토 생성 + String email2 = "mentor2@test.com"; + Member mentor2 = memberService.joinMentor(email2, "멘토2", "다른닉네임", "password123", "Frontend", 5); + + // 두 번째 멘토의 닉네임을 첫 번째 멘토의 닉네임으로 변경 시도 (중복) + MentorUpdateRequest updateRequest = new MentorUpdateRequest("기존닉네임", "AI/ML", 7); + + ResultActions result = mvc + .perform(put("/members/" + mentor2.getId() + "/mentor") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(updateRequest))) + .andDo(print()); + + result.andExpect(status().is4xxClientError()); + } +} \ No newline at end of file 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/MemberAuthControllerTest.java similarity index 68% rename from back/src/test/java/com/back/domain/member/member/controller/MemberControllerTest.java rename to back/src/test/java/com/back/domain/member/member/controller/MemberAuthControllerTest.java index b852205a..d81e3768 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/MemberAuthControllerTest.java @@ -25,7 +25,7 @@ @SpringBootTest @AutoConfigureMockMvc @Transactional -public class MemberControllerTest { +public class MemberAuthControllerTest { @Autowired private MemberService memberService; @@ -57,7 +57,7 @@ void t1() throws Exception { Member member = memberService.findByEmail("user1@example.com").get(); resultActions - .andExpect(handler().handlerType(MemberController.class)) + .andExpect(handler().handlerType(MemberAuthController.class)) .andExpect(handler().methodName("signupMentee")) .andExpect(status().is2xxSuccessful()) .andExpect(jsonPath("$.resultCode").value("200-1")) @@ -80,7 +80,7 @@ void t2() throws Exception { .andDo(print()); resultActions - .andExpect(handler().handlerType(MemberController.class)) + .andExpect(handler().handlerType(MemberAuthController.class)) .andExpect(handler().methodName("sendMentorVerification")) .andExpect(status().is2xxSuccessful()) .andExpect(jsonPath("$.resultCode").value("200-2")) @@ -117,7 +117,7 @@ void t2_1() throws Exception { Member member = memberService.findByEmail(email).get(); resultActions - .andExpect(handler().handlerType(MemberController.class)) + .andExpect(handler().handlerType(MemberAuthController.class)) .andExpect(handler().methodName("signupMentor")) .andExpect(status().is2xxSuccessful()) .andExpect(jsonPath("$.resultCode").value("200-3")) @@ -152,7 +152,7 @@ void t2_2() throws Exception { .andDo(print()); resultActions - .andExpect(handler().handlerType(MemberController.class)) + .andExpect(handler().handlerType(MemberAuthController.class)) .andExpect(handler().methodName("signupMentor")) .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.resultCode").value("400-2")) @@ -183,7 +183,7 @@ void t3() throws Exception { .andDo(print()); resultActions - .andExpect(handler().handlerType(MemberController.class)) + .andExpect(handler().handlerType(MemberAuthController.class)) .andExpect(handler().methodName("signupMentee")) .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.resultCode").value("400-1")) @@ -221,7 +221,7 @@ void t4() throws Exception { .andDo(print()); resultActions - .andExpect(handler().handlerType(MemberController.class)) + .andExpect(handler().handlerType(MemberAuthController.class)) .andExpect(handler().methodName("signupMentor")) .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.resultCode").value("400-2")) @@ -399,7 +399,7 @@ void t10() throws Exception { .andDo(print()); resultActions - .andExpect(handler().handlerType(MemberController.class)) + .andExpect(handler().handlerType(MemberAuthController.class)) .andExpect(handler().methodName("signupMentee")) .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.resultCode").value("400-3")) @@ -437,7 +437,7 @@ void t11() throws Exception { .andDo(print()); resultActions - .andExpect(handler().handlerType(MemberController.class)) + .andExpect(handler().handlerType(MemberAuthController.class)) .andExpect(handler().methodName("signupMentor")) .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.resultCode").value("400-4")) @@ -468,7 +468,7 @@ void t12() throws Exception { // 회원 탈퇴 요청 ResultActions result = mvc .perform( - delete("/auth/me") + post("/auth/me/withdraw") .cookie(accessToken) ) .andDo(print()); @@ -480,8 +480,11 @@ void t12() throws Exception { .andExpect(cookie().maxAge("accessToken", 0)) .andExpect(cookie().maxAge("refreshToken", 0)); - // 탈퇴 후 해당 이메일로 조회했을 때 없어야 함 + // 소프트 삭제이므로 일반 조회에서는 없어야 함 assertThat(memberService.findByEmail(email)).isEmpty(); + + // 하지만 삭제 포함 조회에서는 존재해야 함 (삭제 상태로) + // memberRepository.findByEmailIncludingDeleted(email)로 조회하면 존재해야 함 } @Test @@ -489,233 +492,13 @@ void t12() throws Exception { void t13() throws Exception { // 로그인 없이 회원 탈퇴 시도 ResultActions result = mvc - .perform(delete("/auth/me")) - .andDo(print()); - - result - .andExpect(status().isUnauthorized()); - } - - @Test - @DisplayName("멘티 마이페이지 조회 성공") - void t14() throws Exception { - String email = "mentee@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( - get("/auth/me/mentee") - .cookie(accessToken) - ) - .andDo(print()); - - result - .andExpect(status().isOk()) - .andExpect(jsonPath("$.resultCode").value("200-9")) - .andExpect(jsonPath("$.msg").value("멘티 정보 조회 성공")) - .andExpect(jsonPath("$.data.email").value(email)) - .andExpect(jsonPath("$.data.name").value("멘티유저")) - .andExpect(jsonPath("$.data.nickname").value("멘티닉네임")); - } - - @Test - @DisplayName("멘티 정보 수정 성공") - void t15() throws Exception { - // 멘티 회원가입 - String email = "mentee2@example.com"; - memberService.joinMentee(email, "멘티유저2", "멘티닉네임2", "password123", "Frontend"); - - // 로그인하여 쿠키 받기 - 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( - put("/auth/me/mentee") - .cookie(accessToken) - .contentType(MediaType.APPLICATION_JSON) - .content(""" - { - "nickname": "새로운닉네임", - "interestedField": "Mobile" - } - """) - ) - .andDo(print()); - - result - .andExpect(status().isOk()) - .andExpect(jsonPath("$.resultCode").value("200-10")) - .andExpect(jsonPath("$.msg").value("멘티 정보 수정 성공")); - } - - @Test - @DisplayName("멘토 마이페이지 조회 성공") - void t16() throws Exception { - // 멘토 회원가입 - String email = "mentor@example.com"; - memberService.joinMentor(email, "멘토유저", "멘토닉네임", "password123", "Backend", 5); - - // 로그인하여 쿠키 받기 - 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( - get("/auth/me/mentor") - .cookie(accessToken) - ) - .andDo(print()); - - result - .andExpect(status().isOk()) - .andExpect(jsonPath("$.resultCode").value("200-11")) - .andExpect(jsonPath("$.msg").value("멘토 정보 조회 성공")) - .andExpect(jsonPath("$.data.email").value(email)) - .andExpect(jsonPath("$.data.name").value("멘토유저")) - .andExpect(jsonPath("$.data.nickname").value("멘토닉네임")) - .andExpect(jsonPath("$.data.careerYears").value(5)); - } - - @Test - @DisplayName("멘토 정보 수정 성공") - void t17() throws Exception { - // 멘토 회원가입 - String email = "mentor2@example.com"; - memberService.joinMentor(email, "멘토유저2", "멘토닉네임2", "password123", "Frontend", 3); - - // 로그인하여 쿠키 받기 - 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( - put("/auth/me/mentor") - .cookie(accessToken) - .contentType(MediaType.APPLICATION_JSON) - .content(""" - { - "nickname": "새로운멘토닉네임", - "career": "Fullstack", - "careerYears": 7 - } - """) - ) - .andDo(print()); - - result - .andExpect(status().isOk()) - .andExpect(jsonPath("$.resultCode").value("200-12")) - .andExpect(jsonPath("$.msg").value("멘토 정보 수정 성공")); - } - - @Test - @DisplayName("로그인하지 않은 상태에서 멘티 마이페이지 접근 - 실패") - void t18() throws Exception { - ResultActions result = mvc - .perform(get("/auth/me/mentee")) - .andDo(print()); - - result - .andExpect(status().isUnauthorized()); - } - - @Test - @DisplayName("로그인하지 않은 상태에서 멘토 마이페이지 접근 - 실패") - void t19() throws Exception { - ResultActions result = mvc - .perform(get("/auth/me/mentor")) + .perform(post("/auth/me/withdraw")) .andDo(print()); result .andExpect(status().isUnauthorized()); } - @Test - @DisplayName("로그인하지 않은 상태에서 멘티 정보 수정 - 실패") - void t20() throws Exception { - ResultActions result = mvc - .perform( - put("/auth/me/mentee") - .contentType(MediaType.APPLICATION_JSON) - .content(""" - { - "nickname": "새로운닉네임", - "interestedField": "Mobile" - } - """) - ) - .andDo(print()); - - result - .andExpect(status().isUnauthorized()); - } - - @Test - @DisplayName("로그인하지 않은 상태에서 멘토 정보 수정 - 실패") - void t21() throws Exception { - ResultActions result = mvc - .perform( - put("/auth/me/mentor") - .contentType(MediaType.APPLICATION_JSON) - .content(""" - { - "nickname": "새로운멘토닉네임", - "career": "Fullstack", - "careerYears": 7 - } - """) - ) - .andDo(print()); - - result - .andExpect(status().isUnauthorized()); - } + // TODO: 마이페이지 관련 테스트들은 MemberMyPageControllerTest로 이동됨 } diff --git a/back/src/test/java/com/back/domain/member/member/controller/MemberMyPageControllerTest.java b/back/src/test/java/com/back/domain/member/member/controller/MemberMyPageControllerTest.java new file mode 100644 index 00000000..fcefa3d4 --- /dev/null +++ b/back/src/test/java/com/back/domain/member/member/controller/MemberMyPageControllerTest.java @@ -0,0 +1,265 @@ +package com.back.domain.member.member.controller; + +import com.back.domain.member.member.entity.Member; +import com.back.domain.member.member.service.MemberService; +import jakarta.servlet.http.Cookie; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.transaction.annotation.Transactional; + +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.*; + +@ActiveProfiles("test") +@SpringBootTest +@AutoConfigureMockMvc +@Transactional +public class MemberMyPageControllerTest { + @Autowired + private MemberService memberService; + + @Autowired + private MockMvc mvc; + + @Test + @DisplayName("멘티 마이페이지 조회 성공") + void t1() throws Exception { + String email = "mentee@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( + get("/members/me/mentee") + .cookie(accessToken) + ) + .andDo(print()); + + result + .andExpect(handler().handlerType(MemberMyPageController.class)) + .andExpect(handler().methodName("getMenteeMyPage")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.resultCode").value("200-9")) + .andExpect(jsonPath("$.msg").value("멘티 정보 조회 성공")) + .andExpect(jsonPath("$.data.email").value(email)) + .andExpect(jsonPath("$.data.name").value("멘티유저")) + .andExpect(jsonPath("$.data.nickname").value("멘티닉네임")); + } + + @Test + @DisplayName("멘티 정보 수정 성공") + void t2() throws Exception { + // 멘티 회원가입 + String email = "mentee2@example.com"; + memberService.joinMentee(email, "멘티유저2", "멘티닉네임2", "password123", "Frontend"); + + // 로그인하여 쿠키 받기 + 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( + put("/members/me/mentee") + .cookie(accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "nickname": "새로운닉네임", + "interestedField": "Mobile" + } + """) + ) + .andDo(print()); + + result + .andExpect(handler().handlerType(MemberMyPageController.class)) + .andExpect(handler().methodName("updateMentee")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.resultCode").value("200-10")) + .andExpect(jsonPath("$.msg").value("멘티 정보 수정 성공")); + } + + @Test + @DisplayName("멘토 마이페이지 조회 성공") + void t3() throws Exception { + // 멘토 회원가입 + String email = "mentor@example.com"; + memberService.joinMentor(email, "멘토유저", "멘토닉네임", "password123", "Backend", 5); + + // 로그인하여 쿠키 받기 + 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( + get("/members/me/mentor") + .cookie(accessToken) + ) + .andDo(print()); + + result + .andExpect(handler().handlerType(MemberMyPageController.class)) + .andExpect(handler().methodName("getMentorMyPage")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.resultCode").value("200-11")) + .andExpect(jsonPath("$.msg").value("멘토 정보 조회 성공")) + .andExpect(jsonPath("$.data.email").value(email)) + .andExpect(jsonPath("$.data.name").value("멘토유저")) + .andExpect(jsonPath("$.data.nickname").value("멘토닉네임")) + .andExpect(jsonPath("$.data.careerYears").value(5)); + } + + @Test + @DisplayName("멘토 정보 수정 성공") + void t4() throws Exception { + // 멘토 회원가입 + String email = "mentor2@example.com"; + memberService.joinMentor(email, "멘토유저2", "멘토닉네임2", "password123", "Frontend", 3); + + // 로그인하여 쿠키 받기 + 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( + put("/members/me/mentor") + .cookie(accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "nickname": "새로운멘토닉네임", + "career": "Fullstack", + "careerYears": 7 + } + """) + ) + .andDo(print()); + + result + .andExpect(handler().handlerType(MemberMyPageController.class)) + .andExpect(handler().methodName("updateMentor")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.resultCode").value("200-12")) + .andExpect(jsonPath("$.msg").value("멘토 정보 수정 성공")); + } + + @Test + @DisplayName("로그인하지 않은 상태에서 멘티 마이페이지 접근 - 실패") + void t5() throws Exception { + ResultActions result = mvc + .perform(get("/members/me/mentee")) + .andDo(print()); + + result + .andDo(print()) + .andExpect(status().is4xxClientError()); + } + + @Test + @DisplayName("로그인하지 않은 상태에서 멘토 마이페이지 접근 - 실패") + void t6() throws Exception { + ResultActions result = mvc + .perform(get("/members/me/mentor")) + .andDo(print()); + + result + .andDo(print()) + .andExpect(status().is4xxClientError()); + } + + @Test + @DisplayName("로그인하지 않은 상태에서 멘티 정보 수정 - 실패") + void t7() throws Exception { + ResultActions result = mvc + .perform( + put("/members/me/mentee") + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "nickname": "새로운닉네임", + "interestedField": "Mobile" + } + """) + ) + .andDo(print()); + + result + .andDo(print()) + .andExpect(status().is4xxClientError()); + } + + @Test + @DisplayName("로그인하지 않은 상태에서 멘토 정보 수정 - 실패") + void t8() throws Exception { + ResultActions result = mvc + .perform( + put("/members/me/mentor") + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "nickname": "새로운멘토닉네임", + "career": "Fullstack", + "careerYears": 7 + } + """) + ) + .andDo(print()); + + result + .andDo(print()) + .andExpect(status().is4xxClientError()); + } +} \ No newline at end of file