본문 바로가기
Project/깃터디 (gitudy)

[깃터디/Member] 스터디 가입 취소 api 구현

by J-rain 2024. 5. 11.

💡 .java를 클릭시 관련 커밋으로 이동💡

 

 회고 

        // 대기중인 멤버인지 조회
        Optional<StudyMember> existingMember = studyMemberRepository.findByStudyInfoIdAndUserId(studyInfoId, user.getUserId());

        // 멤버가 존재하지 않으면 예외 발생
        if (existingMember.isEmpty()) {
            log.warn(">>>> {} : {} <<<<", user.getUserId(), ExceptionMessage.USER_NOT_STUDY_MEMBER);
            throw new MemberException(ExceptionMessage.USER_NOT_STUDY_MEMBER);
        }

        // 멤버의 상태가 대기중이 아니면 예외 발생
        if (existingMember.get().getStatus() != StudyMemberStatus.STUDY_WAITING) {
            log.warn(">>>> {} : {} <<<<", user.getUserId(), ExceptionMessage.STUDY_WAITING_NOT_MEMBER);
            throw new MemberException(ExceptionMessage.STUDY_WAITING_NOT_MEMBER);
        }

        // 상태가 대기인 멤버 삭제
        studyMemberRepository.delete(existingMember.get());

Java의 Optional 타입은 null 값을 직접 다루는 것을 피할 때 사용된다. 즉, Optional 은 값이 있을 수도 있고, 없을 수도 있는 컨테이너 객체인데 값이 있으면 Optional 은 해당 값을 감싸고, 값이 없으면 Optional.empty() 를 반환한다.

 

Optionl.get() 메서드는 Optional 객체가 갖고 있는 값을 반환한다. 하지만 이 메서드는 Optional 객체가 값이 있을 때만 안전하게 사용할 수 있으므로 먼저 if(existingMember.isEmpty()) 를 통해서 값의 유무를 확인후에 사용하는 것이 안전하다!

만약 existingMember.get()을 호출하기 전에, 실제로 existingMember에 값이 있는지 확인하지 않으면, existingMember가 비어 있다면 예외가 발생할 수 있다!!!

 

 

StudyMemberController.java

    // 스터디 가입 신청 취소
    @ApiResponse(responseCode = "200", description = "스터디 가입 신청 취소 성공")
    @DeleteMapping("/{studyInfoId}/apply")
    public JsonResult<?> applyCancelStudyMember(@AuthenticationPrincipal User user,
                                                @PathVariable(name = "studyInfoId") Long studyInfoId) {

        UserInfoResponse userInfo = authService.findUserInfo(user);

        studyMemberService.applyCancelStudyMember(userInfo, studyInfoId);

        return JsonResult.successOf("Apply cancel StudyMember Success");
    }

스터디 가입 신청 취소 컨트롤러 부분이다.

 

 

StudyMemberService.java

    // 스터디 가입 취소 메서드
    @Transactional
    public void applyCancelStudyMember(UserInfoResponse user, Long studyInfoId) {

        // 스터디 조회 예외처리
        studyInfoRepository.findById(studyInfoId).orElseThrow(() -> {
            log.warn(">>>> {} : {} <<<<", studyInfoId, ExceptionMessage.STUDY_INFO_NOT_FOUND);
            return new StudyInfoException(ExceptionMessage.STUDY_INFO_NOT_FOUND);
        });

        // 대기중인 멤버인지 조회
        Optional<StudyMember> existingMember = studyMemberRepository.findByStudyInfoIdAndUserId(studyInfoId, user.getUserId());

        // 멤버가 존재하지 않으면 예외 발생
        if (existingMember.isEmpty()) {
            log.warn(">>>> {} : {} <<<<", user.getUserId(), ExceptionMessage.USER_NOT_STUDY_MEMBER);
            throw new MemberException(ExceptionMessage.USER_NOT_STUDY_MEMBER);
        }

        // 멤버의 상태가 대기중이 아니면 예외 발생
        if (existingMember.get().getStatus() != StudyMemberStatus.STUDY_WAITING) {
            log.warn(">>>> {} : {} <<<<", user.getUserId(), ExceptionMessage.STUDY_WAITING_NOT_MEMBER);
            throw new MemberException(ExceptionMessage.STUDY_WAITING_NOT_MEMBER);
        }

        // 상태가 대기인 멤버 삭제
        studyMemberRepository.delete(existingMember.get());
    }

가입 신청 서비스 메서드에서 유저가 스터디에 가입했을 경우 StudyMember 객체가 생성되는데 이때

상태필드에는 STUDY_WAITING 으로 저장되도록 구현했었다.

 

따라서 스터디 가입 취소 메서드에서는 가입취소 요청한 유저가 STUDY_WAITING 상태가 아닐 경우 예외처리도 함께 포함해도록 구현했다!

 

 


테스트

 

StudyMemberControllerTest.java

    @Test
    public void 스터디_가입_신청_취소_테스트() throws Exception {
        //given
        User savedUser = userRepository.save(generateAuthUser());

        Map<String, String> map = TokenUtil.createTokenMap(savedUser);
        String accessToken = jwtService.generateAccessToken(map, savedUser);
        String refreshToken = jwtService.generateRefreshToken(map, savedUser);

        StudyInfo studyInfo = StudyInfoFixture.createDefaultPublicStudyInfo(savedUser.getId());
        studyInfoRepository.save(studyInfo);

        when(authService.findUserInfo(any(User.class))).thenReturn(UserInfoResponse.of(savedUser));
        doNothing().when(studyMemberService).applyCancelStudyMember(any(UserInfoResponse.class), any(Long.class));

        //when , then
        mockMvc.perform(delete("/member/" + studyInfo.getId() + "/apply")
                        .contentType(MediaType.APPLICATION_JSON)
                        .header(AUTHORIZATION, createAuthorizationHeader(accessToken, refreshToken)))

                // then
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.res_code").value(200))
                .andExpect(jsonPath("$.res_obj").value("Apply cancel StudyMember Success"));

    }
}

가입 신청 취소 컨트롤러 간단한 테스트이다.

 

 

StudyMemberServiceTest.java

    @Test
    @DisplayName("스터디 가입 신청 취소 테스트")
    public void applyCancelStudyMember() {
        // given
        User leader = UserFixture.generateAuthUser();
        User user1 = UserFixture.generateGoogleUser();
        userRepository.saveAll(List.of(leader, user1));

        StudyInfo studyInfo = StudyInfoFixture.createDefaultPublicStudyInfo(leader.getId());
        studyInfoRepository.save(studyInfo);

        StudyMember waitingMember = StudyMemberFixture.createStudyMemberWaiting(user1.getId(), studyInfo.getId());
        studyMemberRepository.save(waitingMember);

        UserInfoResponse userInfo = authService.findUserInfo(user1);

        // when
        studyMemberService.applyCancelStudyMember(userInfo, studyInfo.getId());
        Optional<StudyMember> cancelledMember = studyMemberRepository.findByStudyInfoIdAndUserId(studyInfo.getId(), userInfo.getUserId());

        // then
        assertFalse(cancelledMember.isPresent());
    }

    @Test
    @DisplayName("스터디 가입 신청 실패 테스트- 대기중인 멤버가 아닐때")
    public void applyCancelStudyMember_fail() {
        // given
        User leader = UserFixture.generateAuthUser();
        User user1 = UserFixture.generateGoogleUser();
        userRepository.saveAll(List.of(leader, user1));

        StudyInfo studyInfo = StudyInfoFixture.createDefaultPublicStudyInfo(leader.getId());
        studyInfoRepository.save(studyInfo);

        StudyMember notWaitingMember = StudyMemberFixture.createStudyMemberResigned(user1.getId(), studyInfo.getId());
        studyMemberRepository.save(notWaitingMember);

        UserInfoResponse userInfo = authService.findUserInfo(user1);

        // then
        MemberException em = assertThrows(MemberException.class, () -> {
            studyMemberService.applyCancelStudyMember(userInfo, studyInfo.getId());
        });

        assertEquals(ExceptionMessage.STUDY_WAITING_NOT_MEMBER.getText(), em.getMessage());
    }

    @Test
    @DisplayName("스터디 가입 신청 실패 테스트- 멤버를 찾을 수 없을때")
    public void applyCancelStudyMember_fail_2() {
        // given
        User leader = UserFixture.generateAuthUser();
        User user1 = UserFixture.generateGoogleUser();
        userRepository.saveAll(List.of(leader, user1));

        StudyInfo studyInfo = StudyInfoFixture.createDefaultPublicStudyInfo(leader.getId());
        studyInfoRepository.save(studyInfo);

        UserInfoResponse userInfo = authService.findUserInfo(user1);

        // then
        MemberException em = assertThrows(MemberException.class, () -> {
            studyMemberService.applyCancelStudyMember(userInfo, studyInfo.getId());
        });

        assertEquals(ExceptionMessage.USER_NOT_STUDY_MEMBER.getText(), em.getMessage());
    }

서비스 테스트에는 역시 경우를 나눠서 테스트했다.

  • 스터디 가입 취소 성공 테스트
  • 스터디 가입 취소 실패 테스트 - 대기중인 상태가 아닌 멤버일때
  • 스터디 가입 취소 실패 테스트 - 멤버 객체를 찾을 수 없을 때

댓글