Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat/27] 친구 요청 수락 API 구현 및 테스트 #28

Merged
merged 6 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@

import com.gamegoo.gamegoo_v2.auth.annotation.AuthMember;
import com.gamegoo.gamegoo_v2.common.ApiResponse;
import com.gamegoo.gamegoo_v2.friend.dto.SendFriendRequestResponse;
import com.gamegoo.gamegoo_v2.friend.dto.FriendRequestResponse;
import com.gamegoo.gamegoo_v2.friend.service.FriendFacadeService;
import com.gamegoo.gamegoo_v2.member.domain.Member;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
Expand All @@ -25,9 +26,17 @@ public class FriendController {
@Operation(summary = "친구 요청 전송 API", description = "대상 회원에게 친구 요청을 전송하는 API 입니다. 대상 회원에게 친구 요청 알림을 전송합니다.")
@Parameter(name = "memberId", description = "친구 요청을 전송할 대상 회원의 id 입니다.")
@PostMapping("/request/{memberId}")
public ApiResponse<SendFriendRequestResponse> sendFriendRequest(
public ApiResponse<FriendRequestResponse> sendFriendRequest(
@PathVariable(name = "memberId") Long targetMemberId, @AuthMember Member member) {
return ApiResponse.ok(friendFacadeService.sendFriendRequest(member, targetMemberId));
}

@Operation(summary = "친구 요청 수락 API", description = "대상 회원이 보낸 친구 요청을 수락 처리하는 API 입니다.")
@Parameter(name = "memberId", description = "친구 요청을 수락할 대상 회원의 id 입니다.")
@PatchMapping("/request/{memberId}/accept")
public ApiResponse<FriendRequestResponse> acceptFriendRequest(@PathVariable(name = "memberId") Long targetMemberId,
@AuthMember Member member) {
return ApiResponse.ok(friendFacadeService.acceptFriendRequest(member, targetMemberId));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,8 @@ private FriendRequest(FriendRequestStatus status, Member fromMember, Member toMe
this.toMember = toMember;
}

public void updateStatus(FriendRequestStatus status) {
this.status = status;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@

@Getter
@Builder
public class SendFriendRequestResponse {
public class FriendRequestResponse {

Long targetMemberId;
String message;

public static SendFriendRequestResponse of(Long targetMemberId, String message) {
return SendFriendRequestResponse.builder()
public static FriendRequestResponse of(Long targetMemberId, String message) {
return FriendRequestResponse.builder()
.targetMemberId(targetMemberId)
.message(message)
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@
import com.gamegoo.gamegoo_v2.member.domain.Member;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface FriendRequestRepository extends JpaRepository<FriendRequest, Long> {

boolean existsByFromMemberAndToMemberAndStatus(Member fromMember, Member toMember, FriendRequestStatus status);

Optional<FriendRequest> findByFromMemberAndToMemberAndStatus(Member fromMember, Member toMember,
FriendRequestStatus status);

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.gamegoo.gamegoo_v2.friend.service;

import com.gamegoo.gamegoo_v2.friend.domain.FriendRequest;
import com.gamegoo.gamegoo_v2.friend.dto.SendFriendRequestResponse;
import com.gamegoo.gamegoo_v2.friend.dto.FriendRequestResponse;
import com.gamegoo.gamegoo_v2.member.domain.Member;
import com.gamegoo.gamegoo_v2.member.service.MemberService;
import lombok.RequiredArgsConstructor;
Expand All @@ -16,12 +16,34 @@ public class FriendFacadeService {
private final FriendService friendService;
private final MemberService memberService;

/**
* 친구 요청 전송 Facade 메소드
*
* @param member
* @param targetMemberId
* @return
*/
@Transactional
public SendFriendRequestResponse sendFriendRequest(Member member, Long targetMemberId) {
public FriendRequestResponse sendFriendRequest(Member member, Long targetMemberId) {
Member targetMember = memberService.findMember(targetMemberId);
FriendRequest friendRequest = friendService.sendFriendRequest(member, targetMember);

return SendFriendRequestResponse.of(friendRequest.getToMember().getId(), "친구 요청 전송 성공");
return FriendRequestResponse.of(friendRequest.getToMember().getId(), "친구 요청 전송 성공");
}

/**
* 친구 요청 수락 Facade 메소드
*
* @param member
* @param targetMemberId
* @return
*/
@Transactional
public FriendRequestResponse acceptFriendRequest(Member member, Long targetMemberId) {
Member targetMember = memberService.findMember(targetMemberId);
FriendRequest friendRequest = friendService.acceptFriendRequest(member, targetMember);

return FriendRequestResponse.of(friendRequest.getFromMember().getId(), "친구 요청 수락 성공");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
import com.gamegoo.gamegoo_v2.common.validator.MemberValidator;
import com.gamegoo.gamegoo_v2.exception.FriendException;
import com.gamegoo.gamegoo_v2.exception.common.ErrorCode;
import com.gamegoo.gamegoo_v2.friend.domain.Friend;
import com.gamegoo.gamegoo_v2.friend.domain.FriendRequest;
import com.gamegoo.gamegoo_v2.friend.domain.FriendRequestStatus;
import com.gamegoo.gamegoo_v2.friend.repository.FriendRepository;
import com.gamegoo.gamegoo_v2.friend.repository.FriendRequestRepository;
import com.gamegoo.gamegoo_v2.member.domain.Member;
import lombok.RequiredArgsConstructor;
Expand All @@ -18,6 +21,7 @@
public class FriendService {

private final FriendRequestRepository friendRequestRepository;
private final FriendRepository friendRepository;
private final BlockValidator blockValidator;
private final MemberValidator memberValidator;
private final FriendValidator friendValidator;
Expand Down Expand Up @@ -53,6 +57,35 @@ public FriendRequest sendFriendRequest(Member member, Member targetMember) {
return friendRequest;
}

/**
* targetMember가 보낸 친구 요청 수락 처리 메소드
*
* @param member
* @param targetMember
* @return
*/
@Transactional
public FriendRequest acceptFriendRequest(Member member, Member targetMember) {
// targetMember로 나 자신을 요청한 경우 검증
validateNotSelf(member, targetMember);

// 수락 대기 상태인 FriendRequest 엔티티 조회 및 검증
FriendRequest friendRequest = friendRequestRepository.findByFromMemberAndToMemberAndStatus(targetMember,
member, FriendRequestStatus.PENDING)
.orElseThrow(() -> new FriendException(ErrorCode.PENDING_FRIEND_REQUEST_NOT_EXIST));

// FriendRequest 엔티티 상태 변경
friendRequest.updateStatus(FriendRequestStatus.ACCEPTED);

// friend 엔티티 생성 및 저장
friendRepository.save(Friend.create(member, targetMember));
friendRepository.save(Friend.create(targetMember, member));

// targetMember에게 친구 요청 수락 알림 생성

return friendRequest;
}

private void validateNotSelf(Member member, Member targetMember) {
if (member.equals(targetMember)) {
throw new FriendException(ErrorCode.FRIEND_BAD_REQUEST);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ void unblockMemberFailedWhenTargetMemberIsNotBlocked() throws Exception {
// when // then
mockMvc.perform(delete(API_URL_PREFIX + "/{memberId}", TARGET_MEMBER_ID))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.message").value("차단 목록에 존재하지 않는 회원입니다."));
.andExpect(jsonPath("$.message").value(ErrorCode.TARGET_MEMBER_NOT_BLOCKED.getMessage()));

}

Expand All @@ -179,7 +179,7 @@ void unblockMemberFailedWhenTargetIsBlind() throws Exception {
// when // then
mockMvc.perform(delete(API_URL_PREFIX + "/{memberId}", TARGET_MEMBER_ID))
.andExpect(status().isForbidden())
.andExpect(jsonPath("$.message").value("대상 회원이 탈퇴했습니다."));
.andExpect(jsonPath("$.message").value(ErrorCode.TARGET_MEMBER_DEACTIVATED.getMessage()));
}

}
Expand Down Expand Up @@ -211,7 +211,7 @@ void deleteBlockFailedWhenTargetMemberIsNotBlocked() throws Exception {
// when // then
mockMvc.perform(delete(API_URL_PREFIX + "/delete/{memberId}", TARGET_MEMBER_ID))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.message").value("차단 목록에 존재하지 않는 회원입니다."));
.andExpect(jsonPath("$.message").value(ErrorCode.TARGET_MEMBER_NOT_BLOCKED.getMessage()));

}

Expand All @@ -225,7 +225,7 @@ void deleteBlockFailedWhenTargetIsBlind() throws Exception {
// when // then
mockMvc.perform(delete(API_URL_PREFIX + "/delete/{memberId}", TARGET_MEMBER_ID))
.andExpect(status().isForbidden())
.andExpect(jsonPath("$.message").value("차단 목록에서 삭제 불가한 회원입니다."));
.andExpect(jsonPath("$.message").value(ErrorCode.DELETE_BLOCKED_MEMBER_FAILED.getMessage()));
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import com.gamegoo.gamegoo_v2.exception.MemberException;
import com.gamegoo.gamegoo_v2.exception.common.ErrorCode;
import com.gamegoo.gamegoo_v2.friend.controller.FriendController;
import com.gamegoo.gamegoo_v2.friend.dto.SendFriendRequestResponse;
import com.gamegoo.gamegoo_v2.friend.dto.FriendRequestResponse;
import com.gamegoo.gamegoo_v2.friend.service.FriendFacadeService;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
Expand All @@ -17,6 +17,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willThrow;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
Expand All @@ -32,13 +33,13 @@ class FriendControllerTest extends ControllerTestSupport {

@Nested
@DisplayName("친구 요청 전송")
class sendFriendRequest {
class SendFriendRequestTest {

@DisplayName("친구 요청 전송 성공")
@Test
void sendFriendRequestSucceeds() throws Exception {
// given
SendFriendRequestResponse response = SendFriendRequestResponse.builder()
FriendRequestResponse response = FriendRequestResponse.builder()
.targetMemberId(TARGET_MEMBER_ID)
.message("친구 요청 전송 성공")
.build();
Expand Down Expand Up @@ -146,5 +147,55 @@ void sendFriendRequestFailedWhenPendingRequestToMeExists() throws Exception {

}

@Nested
@DisplayName("친구 요청 수락")
class AcceptFriendRequestTest {

@DisplayName("친구 요청 수락 성공")
@Test
void acceptFriendRequestSucceeds() throws Exception {
// given
FriendRequestResponse response = FriendRequestResponse.builder()
.targetMemberId(TARGET_MEMBER_ID)
.message("친구 요청 수락 성공")
.build();

given(friendFacadeService.acceptFriendRequest(any(), any())).willReturn(response);

// when // then
mockMvc.perform(patch(API_URL_PREFIX + "/request/{memberId}/accept", TARGET_MEMBER_ID))
.andExpect(status().isOk())
.andExpect(jsonPath("$.message").value("OK"))
.andExpect(jsonPath("$.data.message").value("친구 요청 수락 성공"))
.andExpect(jsonPath("$.data.targetMemberId").value(TARGET_MEMBER_ID));
}

@DisplayName("친구 요청 수락 실패: 본인 id를 요청한 경우 에러 응답을 반환한다.")
@Test
void acceptFriendRequestFailedWhenTargetIsSelf() throws Exception {
// given
willThrow(new FriendException(ErrorCode.FRIEND_BAD_REQUEST))
.given(friendFacadeService).acceptFriendRequest(any(), eq(TARGET_MEMBER_ID));

// when // then
mockMvc.perform(patch(API_URL_PREFIX + "/request/{memberId}/accept", TARGET_MEMBER_ID))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.message").value(ErrorCode.FRIEND_BAD_REQUEST.getMessage()));
}

@DisplayName("친구 요청 수락 실패: PENDING 상태인 친구 요청이 없는 경우 에러 응답을 반환한다.")
@Test
void acceptFriendRequestFailedWhenNoPendingRequest() throws Exception {
// given
willThrow(new FriendException(ErrorCode.PENDING_FRIEND_REQUEST_NOT_EXIST))
.given(friendFacadeService).acceptFriendRequest(any(), eq(TARGET_MEMBER_ID));

// when // then
mockMvc.perform(patch(API_URL_PREFIX + "/request/{memberId}/accept", TARGET_MEMBER_ID))
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.message").value(ErrorCode.PENDING_FRIEND_REQUEST_NOT_EXIST.getMessage()));
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import com.gamegoo.gamegoo_v2.exception.common.ErrorCode;
import com.gamegoo.gamegoo_v2.friend.domain.Friend;
import com.gamegoo.gamegoo_v2.friend.domain.FriendRequest;
import com.gamegoo.gamegoo_v2.friend.dto.SendFriendRequestResponse;
import com.gamegoo.gamegoo_v2.friend.dto.FriendRequestResponse;
import com.gamegoo.gamegoo_v2.friend.repository.FriendRepository;
import com.gamegoo.gamegoo_v2.friend.repository.FriendRequestRepository;
import com.gamegoo.gamegoo_v2.friend.service.FriendFacadeService;
Expand All @@ -24,6 +24,7 @@

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertTrue;

@ActiveProfiles("test")
@SpringBootTest
Expand Down Expand Up @@ -56,12 +57,12 @@ void sendFriendRequestSucceeds() {
Member targetMember = createMember("target@naver.com", "target");

// when
SendFriendRequestResponse sendFriendRequestResponse = friendFacadeService.sendFriendRequest(member,
FriendRequestResponse response = friendFacadeService.sendFriendRequest(member,
targetMember.getId());

// then
assertThat(sendFriendRequestResponse.getTargetMemberId()).isEqualTo(targetMember.getId());
assertThat(sendFriendRequestResponse.getMessage()).isEqualTo("친구 요청 전송 성공");
assertThat(response.getTargetMemberId()).isEqualTo(targetMember.getId());
assertThat(response.getMessage()).isEqualTo("친구 요청 전송 성공");
}

@DisplayName("친구 요청 전송 실패: 본인 id를 요청한 경우 예외가 발생한다.")
Expand Down Expand Up @@ -173,6 +174,49 @@ void sendFriendRequest_shouldThrowWhenPendingRequestToMeExists() {
.hasMessage(ErrorCode.TARGET_PENDING_FRIEND_REQUEST_EXIST.getMessage());
}

@DisplayName("친구 요청 수락 성공")
@Test
void acceptFriendRequestSucceeds() {
// given
Member member = createMember(MEMBER_EMAIL, MEMBER_GAMENAME);
Member targetMember = createMember("target@naver.com", "target");

// 상대 -> 나 친구 요청 생성
friendRequestRepository.save(FriendRequest.create(targetMember, member));

// when
FriendRequestResponse response = friendFacadeService.acceptFriendRequest(member, targetMember.getId());

// then
assertThat(response.getTargetMemberId()).isEqualTo(targetMember.getId());
assertTrue(friendRepository.existsByFromMemberAndToMember(member, targetMember));
}

@DisplayName("친구 요청 수락 실패: 본인 id를 요청한 경우 예외가 발생한다.")
@Test
void acceptFriendRequest_shouldThrowWhenTargetIsSelf() {
// given
Member member = createMember(MEMBER_EMAIL, MEMBER_GAMENAME);

// when // then
assertThatThrownBy(() -> friendFacadeService.acceptFriendRequest(member, member.getId()))
.isInstanceOf(FriendException.class)
.hasMessage(ErrorCode.FRIEND_BAD_REQUEST.getMessage());
}

@DisplayName("친구 요청 수락 실패: PENDING 상태인 친구 요청이 없는 경우 예외가 발생한다")
@Test
void accpetFriendRequest_shouldThrowWhenNoPendingRequest() {
// given
Member member = createMember(MEMBER_EMAIL, MEMBER_GAMENAME);
Member targetMember = createMember("target@naver.com", "target");

// when // then
assertThatThrownBy(() -> friendFacadeService.acceptFriendRequest(member, targetMember.getId()))
.isInstanceOf(FriendException.class)
.hasMessage(ErrorCode.PENDING_FRIEND_REQUEST_NOT_EXIST.getMessage());
}

private Member createMember(String email, String gameName) {
return memberRepository.save(Member.builder()
.email(email)
Expand Down
Loading