💡 .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가 비어 있다면 예외가 발생할 수 있다!!!
// 스터디 가입 신청 취소
@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");
}
스터디 가입 신청 취소 컨트롤러 부분이다.
// 스터디 가입 취소 메서드
@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"));
}
}
가입 신청 취소 컨트롤러 간단한 테스트이다.
@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());
}
서비스 테스트에는 역시 경우를 나눠서 테스트했다.
- 스터디 가입 취소 성공 테스트
- 스터디 가입 취소 실패 테스트 - 대기중인 상태가 아닌 멤버일때
- 스터디 가입 취소 실패 테스트 - 멤버 객체를 찾을 수 없을 때
'Project > 깃터디 (gitudy)' 카테고리의 다른 글
[깃터디/Member] 스터디원 강퇴, 스터디원 탈퇴 api 구현 (0) | 2024.05.11 |
---|---|
[깃터디/Member] 스터디장 가입 신청 승인/거부 api 구현 (0) | 2024.05.11 |
[깃터디/Member] 스터디 가입 신청 api 구현 (0) | 2024.05.11 |
[깃터디/Todo] 스터디원의 Todo 완료여부 조회 api 구현 (0) | 2024.05.11 |
[깃터디/Todo] Todo 삭제 api 구현 (0) | 2024.05.11 |
댓글