diff --git a/src/main/java/com/example/spot/api/code/status/ErrorStatus.java b/src/main/java/com/example/spot/api/code/status/ErrorStatus.java index b23b7a74..6687eb99 100644 --- a/src/main/java/com/example/spot/api/code/status/ErrorStatus.java +++ b/src/main/java/com/example/spot/api/code/status/ErrorStatus.java @@ -75,6 +75,8 @@ public enum ErrorStatus implements BaseErrorCode { _ONLY_STUDY_MEMBER_CAN_ACCESS_SCHEDULE(HttpStatus.FORBIDDEN, "STUDY4015", "스터디 멤버만 일정에 접근할 수 있습니다."), _ONLY_STUDY_MEMBER_CAN_ACCESS_MEMBERS(HttpStatus.FORBIDDEN, "STUDY4016", "스터디 멤버만 회원 목록에 접근할 수 있습니다."), _ALREADY_STUDY_MEMBER(HttpStatus.BAD_REQUEST, "STUDY4017", "이미 스터디 멤버입니다."), + _STUDY_OWNER_ONLY_CAN_TERMINATE(HttpStatus.BAD_REQUEST, "STUDY4018", "스터디장만 스터디를 종료할 수 있습니다."), + _STUDY_ALREADY_TERMINATED(HttpStatus.BAD_REQUEST, "STUDY4019", "이미 종료된 스터디입니다."), //스터디 게시글 관련 에러 _STUDY_POST_NOT_FOUND(HttpStatus.NOT_FOUND, "POST4001", "스터디 게시글을 찾을 수 없습니다."), diff --git a/src/main/java/com/example/spot/service/memberstudy/MemberStudyCommandServiceImpl.java b/src/main/java/com/example/spot/service/memberstudy/MemberStudyCommandServiceImpl.java index 8cc85612..2a30badb 100644 --- a/src/main/java/com/example/spot/service/memberstudy/MemberStudyCommandServiceImpl.java +++ b/src/main/java/com/example/spot/service/memberstudy/MemberStudyCommandServiceImpl.java @@ -87,7 +87,7 @@ public StudyWithdrawalResponseDTO.WithdrawalDTO withdrawFromStudy(Long studyId) .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND)); // 참여가 승인되지 않은 스터디는 탈퇴할 수 없음 - if (memberStudy.getStatus().equals(ApplicationStatus.APPLIED)) { + if (!memberStudy.getStatus().equals(ApplicationStatus.APPROVED)) { throw new StudyHandler(ErrorStatus._STUDY_NOT_APPROVED); } // 스터디장은 스터디를 탈퇴할 수 없음 @@ -107,10 +107,24 @@ public StudyWithdrawalResponseDTO.WithdrawalDTO withdrawFromStudy(Long studyId) */ public StudyTerminationResponseDTO.TerminationDTO terminateStudy(Long studyId) { + // Authorization + Long memberId = SecurityUtils.getCurrentUserId(); + memberRepository.findById(memberId) + .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); Study study = studyRepository.findById(studyId) .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); + MemberStudy memberStudy = memberStudyRepository.findByMemberIdAndStudyIdAndStatus(memberId, studyId, ApplicationStatus.APPROVED) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND)); + + // 스터디장이 아니면 스터디를 종료할 수 없음 + if (memberStudy.getIsOwned().equals(false)) { + throw new StudyHandler(ErrorStatus._STUDY_OWNER_ONLY_CAN_TERMINATE); + } - // 스터디장 확인 로직 빠짐 + // 이미 종료된 스터디는 종료할 수 없음 + if (study.getStatus().equals(Status.OFF)) { + throw new StudyHandler(ErrorStatus._STUDY_ALREADY_TERMINATED); + } study.setStatus(Status.OFF); studyRepository.save(study); diff --git a/src/test/java/com/example/spot/service/memberstudy/MemberStudyCommandServiceTest.java b/src/test/java/com/example/spot/service/memberstudy/MemberStudyCommandServiceTest.java index 8e79e214..1e7bdc42 100644 --- a/src/test/java/com/example/spot/service/memberstudy/MemberStudyCommandServiceTest.java +++ b/src/test/java/com/example/spot/service/memberstudy/MemberStudyCommandServiceTest.java @@ -1,5 +1,6 @@ package com.example.spot.service.memberstudy; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; @@ -8,6 +9,8 @@ import com.example.spot.api.exception.handler.StudyHandler; import com.example.spot.domain.Member; +import com.example.spot.domain.enums.ApplicationStatus; +import com.example.spot.domain.enums.Status; import com.example.spot.domain.mapping.MemberStudy; import com.example.spot.domain.study.Study; import com.example.spot.domain.study.ToDoList; @@ -21,6 +24,9 @@ import java.time.LocalDate; import java.util.Collections; import java.util.Optional; + +import com.example.spot.web.dto.memberstudy.response.StudyTerminationResponseDTO; +import com.example.spot.web.dto.memberstudy.response.StudyWithdrawalResponseDTO; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -89,6 +95,199 @@ void init() { SecurityContextHolder.setContext(securityContext); } + /* ---------------------------- 진행중인 스터디 관련 메서드 ---------------------------- */ + + @Test + @DisplayName("스터디 탈퇴 - (성공)") + void withdrawFromStudy_Success() { + + // given + member = Member.builder() + .id(1L) + .name("회원1") + .build(); + study = Study.builder() + .id(1L) + .title("스터디") + .build(); + memberStudy = MemberStudy.builder() + .member(member) + .study(study) + .isOwned(false) + .status(ApplicationStatus.APPROVED) + .build(); + + when(memberRepository.findById(1L)).thenReturn(Optional.of(member)); + when(studyRepository.findById(1L)).thenReturn(Optional.of(study)); + when(memberStudyRepository.findByMemberIdAndStudyIdAndStatus(1L, 1L, ApplicationStatus.APPROVED)) + .thenReturn(Optional.of(memberStudy)); + + // when + StudyWithdrawalResponseDTO.WithdrawalDTO result = memberStudyCommandService.withdrawFromStudy(1L); + + // then + assertNotNull(result); + assertThat(result.getStudyId()).isEqualTo(1L); + assertThat(result.getStudyName()).isEqualTo("스터디"); + assertThat(result.getMemberId()).isEqualTo(1L); + assertThat(result.getMemberName()).isEqualTo("회원1"); + } + + @Test + @DisplayName("스터디 탈퇴 - 스터디 회원이 아닌 경우 (실패)") + void withdrawFromStudy_NotStudyMember_Fail() { + + // given + member = Member.builder() + .id(1L) + .name("회원1") + .build(); + study = Study.builder() + .id(1L) + .title("스터디") + .build(); + memberStudy = MemberStudy.builder() + .member(member) + .study(study) + .isOwned(false) + .status(ApplicationStatus.APPLIED) + .build(); + + when(memberRepository.findById(1L)).thenReturn(Optional.of(member)); + when(studyRepository.findById(1L)).thenReturn(Optional.of(study)); + when(memberStudyRepository.findByMemberIdAndStudyIdAndStatus(1L, 1L, ApplicationStatus.APPROVED)) + .thenReturn(Optional.empty()); + + // when & then + assertThrows(StudyHandler.class, () -> memberStudyCommandService.withdrawFromStudy(1L)); + } + + @Test + @DisplayName("스터디 탈퇴 - 스터디장인 경우 (실패)") + void withdrawFromStudy_StudyOwner_Fail() { + + // given + member = Member.builder() + .id(1L) + .name("회원1") + .build(); + study = Study.builder() + .id(1L) + .title("스터디") + .build(); + memberStudy = MemberStudy.builder() + .member(member) + .study(study) + .isOwned(true) + .status(ApplicationStatus.APPROVED) + .build(); + + when(memberRepository.findById(1L)).thenReturn(Optional.of(member)); + when(studyRepository.findById(1L)).thenReturn(Optional.of(study)); + when(memberStudyRepository.findByMemberIdAndStudyIdAndStatus(1L, 1L, ApplicationStatus.APPROVED)) + .thenReturn(Optional.of(memberStudy)); + + // when & then + assertThrows(StudyHandler.class, () -> memberStudyCommandService.withdrawFromStudy(1L)); + } + + @Test + @DisplayName("스터디 종료 - (성공)") + void terminateStudy_Success() { + + // given + member = Member.builder() + .id(1L) + .name("회원1") + .build(); + study = Study.builder() + .id(1L) + .title("스터디") + .status(Status.ON) + .build(); + memberStudy = MemberStudy.builder() + .member(member) + .study(study) + .isOwned(true) + .status(ApplicationStatus.APPROVED) + .build(); + + when(memberRepository.findById(1L)).thenReturn(Optional.of(member)); + when(studyRepository.findById(1L)).thenReturn(Optional.of(study)); + when(memberStudyRepository.findByMemberIdAndStudyIdAndStatus(1L, 1L, ApplicationStatus.APPROVED)) + .thenReturn(Optional.of(memberStudy)); + + // when + StudyTerminationResponseDTO.TerminationDTO result = memberStudyCommandService.terminateStudy(1L); + + // then + assertNotNull(result); + assertThat(result.getStudyId()).isEqualTo(1L); + assertThat(result.getStudyName()).isEqualTo("스터디"); + assertThat(result.getStatus()).isEqualTo(Status.OFF); + } + + @Test + @DisplayName("스터디 종료 - 스터디장이 아닌 경우 (실패)") + void terminateStudy_NotStudyOwner_Fail() { + + // given + member = Member.builder() + .id(1L) + .name("회원1") + .build(); + study = Study.builder() + .id(1L) + .title("스터디") + .status(Status.ON) + .build(); + memberStudy = MemberStudy.builder() + .member(member) + .study(study) + .isOwned(false) + .status(ApplicationStatus.APPROVED) + .build(); + + when(memberRepository.findById(1L)).thenReturn(Optional.of(member)); + when(studyRepository.findById(1L)).thenReturn(Optional.of(study)); + when(memberStudyRepository.findByMemberIdAndStudyIdAndStatus(1L, 1L, ApplicationStatus.APPROVED)) + .thenReturn(Optional.of(memberStudy)); + + // when & then + assertThrows(StudyHandler.class, () -> memberStudyCommandService.terminateStudy(1L)); + } + + @Test + @DisplayName("스터디 종료 - 진행중인 스터디가 아닌 경우 (실패)") + void terminateStudy_AlreadyTerminated_Fail() { + + // given + member = Member.builder() + .id(1L) + .name("회원1") + .build(); + study = Study.builder() + .id(1L) + .title("스터디") + .status(Status.OFF) + .build(); + memberStudy = MemberStudy.builder() + .member(member) + .study(study) + .isOwned(false) + .status(ApplicationStatus.APPROVED) + .build(); + + when(memberRepository.findById(1L)).thenReturn(Optional.of(member)); + when(studyRepository.findById(1L)).thenReturn(Optional.of(study)); + when(memberStudyRepository.findByMemberIdAndStudyIdAndStatus(1L, 1L, ApplicationStatus.APPROVED)) + .thenReturn(Optional.of(memberStudy)); + + // when & then + assertThrows(StudyHandler.class, () -> memberStudyCommandService.terminateStudy(1L)); + } + + /* ---------------------------- To-Do 생성 관련 메서드 ---------------------------- */ @Test