Skip to content

Commit

Permalink
GETP-113 feat: 피플 좋아요 취소 기능 구현 (#53)
Browse files Browse the repository at this point in the history
* GETP-113 feat: 피플 좋아요 취소 기능 구현

* GETP-113 test: 피플 좋아요 취소 기능 테스트 구현

* GET-P 113 feat: 코드 리뷰 피드백 반영

---------

Co-authored-by: Changyu Shin <[email protected]>
  • Loading branch information
wlgns12370 and scv1702 authored Jul 11, 2024
1 parent b90f2b7 commit 31fb32f
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Optional;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
Expand All @@ -19,6 +21,14 @@ public class PeopleLikeService {
private final ClientService clientService;
private final PeopleLikeRepository peopleLikeRepository;

private PeopleLike get(Optional<PeopleLike> peopleLike) {
return peopleLike.orElseThrow(() -> new BusinessLogicException(PeopleLikeErrorCode.NEVER_LIKED));
}

public PeopleLike getByMemberIdAndPeopleId(Long memberId, Long peopleId) {
return get(peopleLikeRepository.findByPeople_PeopleIdAndClient_ClientId(memberId, peopleId));
}

private void checkPeopleIsAlreadyLiked(Long clientId, Long peopleId) {
if (peopleLikeRepository.existsByClient_ClientIdAndPeople_PeopleId(clientId, peopleId)) {
throw new BusinessLogicException(PeopleLikeErrorCode.ALREADY_LIKED);
Expand All @@ -37,4 +47,10 @@ public void like(Long memberId, Long peopleId) {
.build()
);
}

@Transactional
public void unlike(Long memberId, Long peopleId) {
PeopleLike peopleLike = getByMemberIdAndPeopleId(memberId, peopleId);
peopleLikeRepository.delete(peopleLike);
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package es.princip.getp.domain.people.exception;

import org.springframework.http.HttpStatus;

import es.princip.getp.infra.exception.ErrorCode;
import es.princip.getp.infra.exception.ErrorDescription;
import org.springframework.http.HttpStatus;

public enum PeopleLikeErrorCode implements ErrorCode {
ALREADY_LIKED(HttpStatus.CONFLICT, "이미 좋아요를 누른 피플입니다.");
ALREADY_LIKED(HttpStatus.CONFLICT, "이미 좋아요를 누른 피플입니다."),
NEVER_LIKED(HttpStatus.NOT_FOUND, "해당 피플에게 좋아요를 누른 적이 없습니다.");

private final HttpStatus status;
private final ErrorDescription description;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,7 @@
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/people")
Expand All @@ -37,4 +34,21 @@ public ResponseEntity<ApiSuccessResult<?>> like(
return ResponseEntity.status(HttpStatus.CREATED)
.body(ApiResponse.success(HttpStatus.CREATED));
}

/**
* 피플 좋아요 취소
*
* @param peopleId 좋아요를 취소할 피플 ID
* @param principalDetails 로그인한 사용자 정보
*/
@DeleteMapping("/{peopleId}/likes")
@PreAuthorize("hasRole('CLIENT') and isAuthenticated()")
public ResponseEntity<ApiSuccessResult<?>> unlike(
@PathVariable Long peopleId,
@AuthenticationPrincipal PrincipalDetails principalDetails
) {
peopleLikeService.unlike(principalDetails.getMember().getMemberId(), peopleId);
return ResponseEntity.status(HttpStatus.NO_CONTENT)
.body(ApiResponse.success(HttpStatus.NO_CONTENT));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
import es.princip.getp.domain.people.domain.PeopleLike;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface PeopleLikeRepository extends JpaRepository<PeopleLike, Long> {

boolean existsByClient_ClientIdAndPeople_PeopleId(Long clientId, Long peopleId);

Optional<PeopleLike> findByPeople_PeopleIdAndClient_ClientId(Long peopleId, Long clientId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import es.princip.getp.domain.client.domain.Client;
import es.princip.getp.domain.member.domain.Member;
import es.princip.getp.domain.people.domain.People;
import es.princip.getp.domain.people.domain.PeopleLike;
import es.princip.getp.domain.people.exception.PeopleErrorCode;
import es.princip.getp.domain.people.exception.PeopleLikeErrorCode;
import es.princip.getp.domain.people.repository.PeopleLikeRepository;
Expand All @@ -16,9 +17,12 @@
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.Optional;

import static es.princip.getp.domain.client.fixture.ClientFixture.createClient;
import static es.princip.getp.domain.member.fixture.MemberFixture.createMember;
import static es.princip.getp.domain.people.fixture.PeopleFixture.createPeople;
import static es.princip.getp.domain.people.fixture.PeopleLikeFixture.createPeopleLike;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.BDDAssertions.catchThrowableOfType;
import static org.mockito.ArgumentMatchers.any;
Expand Down Expand Up @@ -51,6 +55,7 @@ class Like {

private final People people = createPeople(member);
private final Client client = createClient();
private final PeopleLike peopleLike = createPeopleLike(client, people);

@DisplayName("피플에게 좋아요를 누른다.")
@Test
Expand Down Expand Up @@ -94,5 +99,28 @@ void like_WhenPeopleIsNotFound_ShouldThrowException() {
BusinessLogicException.class);
assertThat(exception.getErrorCode()).isEqualTo(PeopleErrorCode.PEOPLE_NOT_FOUND);
}

@DisplayName("피플에게 좋아요를 취소한다.")
@Test
void unlike() {
given(peopleLikeRepository.findByPeople_PeopleIdAndClient_ClientId(memberId, peopleId)).willReturn(Optional.of(peopleLike));

peopleLikeService.unlike(memberId, peopleId);

verify(peopleLikeRepository, times(1)).delete(peopleLike);
}

@DisplayName("좋아요를 누르지 않은 피플에게 의뢰자가 좋아요를 취소할 경우 예외를 던진다.")
@Test
void unlike_ThrowExceptionWhenLikerCancelsLikeForPeopleNotLiked() {
given(peopleLikeRepository.findByPeople_PeopleIdAndClient_ClientId(peopleId, memberId)).willThrow(
new BusinessLogicException(PeopleLikeErrorCode.NEVER_LIKED)
);

BusinessLogicException exception =
catchThrowableOfType(() -> peopleLikeService.unlike(memberId, peopleId),
BusinessLogicException.class);
assertThat(exception.getErrorCode()).isEqualTo(PeopleLikeErrorCode.NEVER_LIKED);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package es.princip.getp.domain.people.fixture;

import es.princip.getp.domain.client.domain.Client;
import es.princip.getp.domain.people.domain.People;
import es.princip.getp.domain.people.domain.PeopleLike;

public class PeopleLikeFixture {
public static PeopleLike createPeopleLike(final Client client, final People people) {
return PeopleLike.builder()
.client(client)
.people(people)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;

import static es.princip.getp.domain.member.domain.MemberType.ROLE_CLIENT;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.willDoNothing;
import static org.mockito.BDDMockito.willThrow;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@WebMvcTest({PeopleLikeController.class, PeopleErrorCodeController.class})
Expand All @@ -37,43 +38,73 @@ class Like {
private final Long peopleId = 1L;

@DisplayName("의뢰자는 피플에게 좋아요를 누를 수 있다.")
@WithCustomMockUser(memberType = ROLE_CLIENT)
@WithCustomMockUser(memberType = MemberType.ROLE_CLIENT)
@Test
void like() throws Exception {
willDoNothing().given(peopleLikeService).like(any(), eq(peopleId));

mockMvc.perform(post("/people/{peopleId}/likes", peopleId))
mockMvc.perform(post("/people/{peopleId}/likes", peopleId)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isCreated());
}

@DisplayName("의뢰자는 이미 좋아요를 누른 피플에게 다시 좋아요를 누를 수 없다.")
@WithCustomMockUser(memberType = ROLE_CLIENT)
@DisplayName("의뢰자는 피플에게 중복으로 좋아요를 누를 수 없다.")
@WithCustomMockUser(memberType = MemberType.ROLE_CLIENT)
@Test
void like_WhenPeopleIsAlreadyLiked_ShouldBeFailed() throws Exception {
willThrow(new BusinessLogicException(PeopleLikeErrorCode.ALREADY_LIKED))
.given(peopleLikeService).like(any(), eq(peopleId));

mockMvc.perform(post("/people/{peopleId}/likes", peopleId))
mockMvc.perform(post("/people/{peopleId}/likes", peopleId)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isConflict());
}

@DisplayName("피플은 피플에게 좋아요를 누를 수 없다.")
@WithCustomMockUser(memberType = MemberType.ROLE_PEOPLE)
@Test
void like_WhenMemberTypeIsPeople_ShouldBeFailed() throws Exception {
mockMvc.perform(post("/people/{peopleId}/likes", peopleId))
mockMvc.perform(post("/people/{peopleId}/likes", peopleId)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isForbidden());
}

@DisplayName("의뢰자는 존재하지 않는 피플에게 좋아요를 누를 수 없다.")
@WithCustomMockUser(memberType = ROLE_CLIENT)
@WithCustomMockUser(memberType = MemberType.ROLE_CLIENT)
@Test
void like_WhenPeopleIsNotFound_ShouldBeFailed() throws Exception {
willThrow(new BusinessLogicException(PeopleErrorCode.PEOPLE_NOT_FOUND))
.given(peopleLikeService).like(any(), eq(peopleId));

mockMvc.perform(post("/people/{peopleId}/likes", peopleId))
mockMvc.perform(post("/people/{peopleId}/likes", peopleId)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isNotFound());
}
}

@Nested
class Unlike {

private final Long peopleId = 1L;

@DisplayName("의뢰자는 피플에게 눌렀던 좋아요를 취소를 할 수 있다.")
@WithCustomMockUser(memberType = MemberType.ROLE_CLIENT)
@Test
void unlike() throws Exception {
willDoNothing().given(peopleLikeService).unlike(any(), eq(peopleId));

mockMvc.perform(delete("/people/{peopleId}/likes", peopleId)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isNoContent());
}

@DisplayName("피플은 피플에게 좋아요를 취소할 수 없다.")
@WithCustomMockUser(memberType = MemberType.ROLE_PEOPLE)
@Test
void unlike_WhenMemberTypeIsPeople_ShouldBeFailed() throws Exception {
mockMvc.perform(delete("/people/{peopleId}/likes", peopleId)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isForbidden());
}
}
}

0 comments on commit 31fb32f

Please sign in to comment.