Skip to content

Commit

Permalink
Feat: 게시글 좋아요 기능 구현 (#66)
Browse files Browse the repository at this point in the history
* #65 - feat: 게시글 좋아요 생성 로직 구현

PostLike 객체 내부에서 post.increaseLikeCount() 를 호출하는 형식으로 함

* #65 - test: 서비스 테스트와 컨트롤러 테스트 구현
  • Loading branch information
GGHDMS authored Feb 4, 2024
1 parent 3ad90d5 commit b007f2f
Show file tree
Hide file tree
Showing 7 changed files with 242 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package cotato.bookitlist.post.controller;

import cotato.bookitlist.config.security.jwt.AuthDetails;
import cotato.bookitlist.post.service.PostLikeService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
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.servlet.support.ServletUriComponentsBuilder;

import java.net.URI;

@RestController
@RequiredArgsConstructor
@RequestMapping("/posts/{post-id}/likes")
public class PostLikeController {

private final PostLikeService postLikeService;

@PostMapping
public ResponseEntity<Void> registerLike(
@PathVariable("post-id") Long postId,
@AuthenticationPrincipal AuthDetails details
) {
Long postLikeId = postLikeService.registerLike(postId, details.getId());

URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(postLikeId)
.toUri();

return ResponseEntity.created(location).build();
}
}
4 changes: 4 additions & 0 deletions src/main/java/cotato/bookitlist/post/domain/Post.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,8 @@ public void updatePost(Member member, String title, String content) {
this.title = title;
this.content = content;
}

public void increaseLikeCount(){
this.likeCount++;
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
package cotato.bookitlist.mark.domain;
package cotato.bookitlist.post.domain;

import cotato.bookitlist.member.domain.Member;
import cotato.bookitlist.post.domain.Post;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class LikePost{
public class PostLike {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "like_post_id")
@Column(name = "post_like_id")
private Long id;

@ManyToOne
Expand All @@ -22,4 +23,17 @@ public class LikePost{
@ManyToOne
@JoinColumn(name = "post_id")
private Post post;

private PostLike(Member member, Post post) {
this.member = member;
this.post = post;
}

public static PostLike of(Member member, Post post) {
return new PostLike(member, post);
}

public void increasePostLikeCount() {
post.increaseLikeCount();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package cotato.bookitlist.post.repository;

import cotato.bookitlist.post.domain.PostLike;
import org.springframework.data.jpa.repository.JpaRepository;

public interface PostLikeRepository extends JpaRepository<PostLike, Long> {
}
32 changes: 32 additions & 0 deletions src/main/java/cotato/bookitlist/post/service/PostLikeService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package cotato.bookitlist.post.service;

import cotato.bookitlist.member.domain.Member;
import cotato.bookitlist.member.repository.MemberRepository;
import cotato.bookitlist.post.domain.Post;
import cotato.bookitlist.post.domain.PostLike;
import cotato.bookitlist.post.repository.PostLikeRepository;
import cotato.bookitlist.post.repository.PostRepository;
import jakarta.persistence.EntityNotFoundException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
@RequiredArgsConstructor
public class PostLikeService {
private final PostRepository postRepository;
private final PostLikeRepository postLikeRepository;
private final MemberRepository memberRepository;

public Long registerLike(Long postId, Long memberId) {
Post post = postRepository.findById(postId)
.orElseThrow(() -> new EntityNotFoundException("책을 찾을 수 없습니다."));
Member member = memberRepository.getReferenceById(memberId);

PostLike postLike = PostLike.of(member, post);
postLike.increasePostLikeCount();

return postLikeRepository.save(postLike).getId();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package cotato.bookitlist.post.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import cotato.bookitlist.annotation.WithCustomMockUser;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.transaction.annotation.Transactional;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@Transactional
@SpringBootTest
@AutoConfigureMockMvc
@DisplayName("게시글 좋아요 컨트롤러 테스트")
@ActiveProfiles("test")
class PostLikeControllerTest {

@Autowired
MockMvc mockMvc;

@Autowired
ObjectMapper objectMapper;

@Test
@WithCustomMockUser
@DisplayName("게시글 좋아요를 생성한다")
void givenPostId_whenRegisteringPostLike_thenRegisterPostLike() throws Exception {
//given

//when & then
mockMvc.perform(post("/posts/1/likes")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isCreated())
.andExpect(header().exists("Location"))
;
}

@Test
@WithCustomMockUser
@DisplayName("존재하지 않는 게시글에 좋아요를 요청하면 에러를 반환한다.")
void givenNonExistedPostId_whenRegisteringPostLike_thenReturnErrorResponse() throws Exception {
//given

//when & then
mockMvc.perform(post("/posts/10/likes")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.message").value("책을 찾을 수 없습니다."))
;
}



}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package cotato.bookitlist.post.service;

import cotato.bookitlist.book.domain.entity.Book;
import cotato.bookitlist.config.security.oauth.AuthProvider;
import cotato.bookitlist.member.domain.Member;
import cotato.bookitlist.member.repository.MemberRepository;
import cotato.bookitlist.post.domain.Post;
import cotato.bookitlist.post.domain.PostLike;
import cotato.bookitlist.post.repository.PostLikeRepository;
import cotato.bookitlist.post.repository.PostRepository;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;

import java.time.LocalDate;
import java.util.Optional;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;

@DisplayName("게시글 좋아요 서비스 테스트")
@ExtendWith(MockitoExtension.class)
class PostLikeServiceTest {

@InjectMocks
PostLikeService sut;
@Mock
PostRepository postRepository;
@Mock
PostLikeRepository postLikeRepository;
@Mock
MemberRepository memberRepository;

@Test
@DisplayName("게시글 좋아요를 생성한다.")
void givenPostId_whenRegisteringPostLike_thenRegisterPostLike() throws Exception {
//given
Long postId = 1L;
Long memberId = 1L;
Post post = createPost();
Member member = createMember();

given(postRepository.findById(anyLong())).willReturn(Optional.of(post));
given(memberRepository.getReferenceById(anyLong())).willReturn(member);
given(postLikeRepository.save(any(PostLike.class))).willReturn(createPostLike(post, member));

//when
sut.registerLike(postId, memberId);

//then
assertThat(post.getLikeCount()).isEqualTo(1);
then(postRepository).should().findById(anyLong());
then(memberRepository).should().getReferenceById(anyLong());
then(postLikeRepository).should().save(any(PostLike.class));
}

Post createPost() {
return Post.of(createMember(), createBook(), "title", "content");
}

Book createBook() {
return Book.of("title", "author", "pubisher", LocalDate.now(), "description", "link", "isbn13", 10000, "cover");
}

Member createMember() {
return new Member("email", "name", "oauth2Id", AuthProvider.KAKAO);
}

PostLike createPostLike(Post post, Member member) {
PostLike postLike = PostLike.of(member, post);
ReflectionTestUtils.setField(postLike, "id", 1L);
return postLike;
}

}

0 comments on commit b007f2f

Please sign in to comment.