diff --git a/src/main/java/team05/integrated_feed_backend/IntegratedFeedBackendApplication.java b/src/main/java/team05/integrated_feed_backend/IntegratedFeedBackendApplication.java index cdbeb07..715e0f6 100644 --- a/src/main/java/team05/integrated_feed_backend/IntegratedFeedBackendApplication.java +++ b/src/main/java/team05/integrated_feed_backend/IntegratedFeedBackendApplication.java @@ -3,8 +3,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.scheduling.annotation.EnableAsync; @SpringBootApplication +@EnableAsync @EnableFeignClients(basePackages = "team05.integrated_feed_backend.infra.sns.api") public class IntegratedFeedBackendApplication { diff --git a/src/main/java/team05/integrated_feed_backend/common/code/StatusCode.java b/src/main/java/team05/integrated_feed_backend/common/code/StatusCode.java index 95af613..31d9c27 100644 --- a/src/main/java/team05/integrated_feed_backend/common/code/StatusCode.java +++ b/src/main/java/team05/integrated_feed_backend/common/code/StatusCode.java @@ -18,6 +18,7 @@ public enum StatusCode { * 400 번대 CODE **/ METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED, "요청 경로가 지원되지 않습니다."), + POST_NOT_EXIST(HttpStatus.NOT_FOUND, "존재하지 않는 게시물입니다."), USER_NOT_FOUND(HttpStatus.NOT_FOUND, "요청된 사용자를 찾을 수 없습니다."), UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "인증 오류가 발생했습니다."), diff --git a/src/main/java/team05/integrated_feed_backend/exception/GlobalExceptionHandler.java b/src/main/java/team05/integrated_feed_backend/exception/GlobalExceptionHandler.java index 2d9afcc..9bc52ef 100644 --- a/src/main/java/team05/integrated_feed_backend/exception/GlobalExceptionHandler.java +++ b/src/main/java/team05/integrated_feed_backend/exception/GlobalExceptionHandler.java @@ -12,6 +12,7 @@ import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; import com.fasterxml.jackson.databind.exc.InvalidFormatException; @@ -28,6 +29,7 @@ @Slf4j @RequiredArgsConstructor +@RestControllerAdvice public class GlobalExceptionHandler { /** diff --git a/src/main/java/team05/integrated_feed_backend/module/post/controller/PostController.java b/src/main/java/team05/integrated_feed_backend/module/post/controller/PostController.java index c90aa4c..4f27b99 100644 --- a/src/main/java/team05/integrated_feed_backend/module/post/controller/PostController.java +++ b/src/main/java/team05/integrated_feed_backend/module/post/controller/PostController.java @@ -3,7 +3,10 @@ import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import lombok.RequiredArgsConstructor; @@ -11,12 +14,15 @@ import team05.integrated_feed_backend.common.code.StatusCode; import team05.integrated_feed_backend.module.post.dto.request.PostSearchReq; import team05.integrated_feed_backend.module.post.dto.response.PostSearchRes; +import team05.integrated_feed_backend.module.post.service.PostService; @RestController @RequiredArgsConstructor @RequestMapping("/api/posts") public class PostController implements PostControllerDocs { + private final PostService postService; + @Override @GetMapping public BaseApiResponse getPosts( @@ -28,4 +34,20 @@ public BaseApiResponse getPosts( return new BaseApiResponse<>(HttpStatus.OK, StatusCode.OK.getMessage(), res); } + // 좋아요 수 증가시키는 api + @ResponseStatus(HttpStatus.OK) + @PatchMapping("/{postId}/like") + public BaseApiResponse increaseLikeCount(@PathVariable(name = "postId") Long postId) { + postService.increaseLikeCount(postId); + return BaseApiResponse.of(StatusCode.OK); + } + + // 공유 수 증가시키는 api + @ResponseStatus(HttpStatus.OK) + @PatchMapping("/{postId}/share") + public BaseApiResponse increaseShareCount(@PathVariable(name = "postId") Long postId) { + postService.increaseShareCount(postId); + return BaseApiResponse.of(StatusCode.OK); + } + } diff --git a/src/main/java/team05/integrated_feed_backend/module/post/controller/PostControllerDocs.java b/src/main/java/team05/integrated_feed_backend/module/post/controller/PostControllerDocs.java index b2caeda..5948c2a 100644 --- a/src/main/java/team05/integrated_feed_backend/module/post/controller/PostControllerDocs.java +++ b/src/main/java/team05/integrated_feed_backend/module/post/controller/PostControllerDocs.java @@ -2,6 +2,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import team05.integrated_feed_backend.common.BaseApiResponse; import team05.integrated_feed_backend.module.post.dto.request.PostSearchReq; @@ -16,4 +17,17 @@ BaseApiResponse getPosts( PostSearchReq postSearchReq ); + @Operation(summary = "게시물 좋아요 수 올리기") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "게시물 좋아요 수가 증가되었습니다.", useReturnTypeSchema = true), + @ApiResponse(responseCode = "404", description = "존재하지 않는 게시물입니다.", useReturnTypeSchema = true), + }) + BaseApiResponse increaseLikeCount(Long postId); + + @Operation(summary = "게시물 공유 수 올리기") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "게시물 공유 수가 증가되었습니다.", useReturnTypeSchema = true), + @ApiResponse(responseCode = "404", description = "존재하지 않는 게시물입니다.", useReturnTypeSchema = true), + }) + BaseApiResponse increaseShareCount(Long postId); } \ No newline at end of file diff --git a/src/main/java/team05/integrated_feed_backend/module/post/entity/Post.java b/src/main/java/team05/integrated_feed_backend/module/post/entity/Post.java index 4f5e075..c5a597a 100644 --- a/src/main/java/team05/integrated_feed_backend/module/post/entity/Post.java +++ b/src/main/java/team05/integrated_feed_backend/module/post/entity/Post.java @@ -55,4 +55,13 @@ public class Post extends BaseEntity { @Builder.Default private List postHashtags = new ArrayList<>(); + // 좋아요 수 증가시키는 메서드 + public void increaseLikeCount() { + this.likeCount += 1; + } + + // 공유 수 증가시키는 메서드 + public void increaseShareCount() { + this.shareCount += 1; + } } diff --git a/src/main/java/team05/integrated_feed_backend/module/post/event/LikeCountIncreasedEvent.java b/src/main/java/team05/integrated_feed_backend/module/post/event/LikeCountIncreasedEvent.java new file mode 100644 index 0000000..d27a93b --- /dev/null +++ b/src/main/java/team05/integrated_feed_backend/module/post/event/LikeCountIncreasedEvent.java @@ -0,0 +1,12 @@ +package team05.integrated_feed_backend.module.post.event; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import team05.integrated_feed_backend.common.enums.SocialMediaType; + +@Getter +@AllArgsConstructor +public class LikeCountIncreasedEvent { + private final Long postId; + private final SocialMediaType type; +} diff --git a/src/main/java/team05/integrated_feed_backend/module/post/event/ShareCountIncreasedEvent.java b/src/main/java/team05/integrated_feed_backend/module/post/event/ShareCountIncreasedEvent.java new file mode 100644 index 0000000..c7f2d59 --- /dev/null +++ b/src/main/java/team05/integrated_feed_backend/module/post/event/ShareCountIncreasedEvent.java @@ -0,0 +1,12 @@ +package team05.integrated_feed_backend.module.post.event; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import team05.integrated_feed_backend.common.enums.SocialMediaType; + +@Getter +@AllArgsConstructor +public class ShareCountIncreasedEvent { + private final Long postId; + private final SocialMediaType type; +} \ No newline at end of file diff --git a/src/main/java/team05/integrated_feed_backend/module/post/event/listener/PostEventListener.java b/src/main/java/team05/integrated_feed_backend/module/post/event/listener/PostEventListener.java new file mode 100644 index 0000000..00f8ac9 --- /dev/null +++ b/src/main/java/team05/integrated_feed_backend/module/post/event/listener/PostEventListener.java @@ -0,0 +1,56 @@ +package team05.integrated_feed_backend.module.post.event.listener; + +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import team05.integrated_feed_backend.common.enums.SocialMediaType; +import team05.integrated_feed_backend.infra.sns.adapter.FacebookAdapter; +import team05.integrated_feed_backend.infra.sns.adapter.InstagramAdapter; +import team05.integrated_feed_backend.infra.sns.adapter.TwitterAdapter; +import team05.integrated_feed_backend.module.post.event.LikeCountIncreasedEvent; +import team05.integrated_feed_backend.module.post.event.ShareCountIncreasedEvent; + +@Component +@RequiredArgsConstructor +@Slf4j +public class PostEventListener { + + private final FacebookAdapter facebookAdapter; + private final TwitterAdapter twitterAdapter; + private final InstagramAdapter instagramAdapter; + + @Async + @EventListener + public void handleLikeCountIncreasedEvent(LikeCountIncreasedEvent event) { + Long postId = event.getPostId(); + SocialMediaType type = event.getType(); + log.info("Asynchronously handling like count increase event for post ID: {}", postId); + + // 외부 API 호출 + switch (type) { + case FACEBOOK -> facebookAdapter.increaseLikeCount(postId); + case INSTAGRAM -> instagramAdapter.increaseLikeCount(postId); + case TWITTER -> twitterAdapter.increaseLikeCount(postId); + default -> log.error(postId + " 게시물의 " + type + ": facebook, instagram, twitter 중 하나로 설정되어 있지 않습니다."); + } + } + + @Async + @EventListener + public void handleShareCountIncreasedEvent(ShareCountIncreasedEvent event) { + Long postId = event.getPostId(); + SocialMediaType type = event.getType(); + log.info("Asynchronously handling share count increase event for post ID: {}", postId); + + // 외부 API 호출 + switch (type) { + case FACEBOOK -> facebookAdapter.increaseShareCount(postId); + case INSTAGRAM -> instagramAdapter.increaseShareCount(postId); + case TWITTER -> twitterAdapter.increaseShareCount(postId); + default -> log.error(postId + " 게시물의 " + type + ": facebook, instagram, twitter 중 하나로 설정되어 있지 않습니다."); + } + } +} \ No newline at end of file diff --git a/src/main/java/team05/integrated_feed_backend/module/post/event/publisher/PostEventPublisher.java b/src/main/java/team05/integrated_feed_backend/module/post/event/publisher/PostEventPublisher.java new file mode 100644 index 0000000..99b4b66 --- /dev/null +++ b/src/main/java/team05/integrated_feed_backend/module/post/event/publisher/PostEventPublisher.java @@ -0,0 +1,31 @@ +package team05.integrated_feed_backend.module.post.event.publisher; + +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import team05.integrated_feed_backend.common.enums.SocialMediaType; +import team05.integrated_feed_backend.module.post.event.LikeCountIncreasedEvent; +import team05.integrated_feed_backend.module.post.event.ShareCountIncreasedEvent; + +@Component +@RequiredArgsConstructor +@Slf4j +public class PostEventPublisher { + + private final ApplicationEventPublisher eventPublisher; + + @Async + public void publishLikeCountIncreasedEvent(Long postId, SocialMediaType type) { + eventPublisher.publishEvent(new LikeCountIncreasedEvent(postId, type)); + log.info("LikeCountIncreasedEvent published asynchronously for post ID: {}", postId); + } + + @Async + public void publishShareCountIncreasedEvent(Long postId, SocialMediaType type) { + eventPublisher.publishEvent(new ShareCountIncreasedEvent(postId, type)); + log.info("ShareCountIncreasedEvent published asynchronously for post ID: {}", postId); + } +} diff --git a/src/main/java/team05/integrated_feed_backend/module/post/repository/.gitkeep b/src/main/java/team05/integrated_feed_backend/module/post/repository/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/team05/integrated_feed_backend/module/post/repository/PostRepository.java b/src/main/java/team05/integrated_feed_backend/module/post/repository/PostRepository.java new file mode 100644 index 0000000..58bf3db --- /dev/null +++ b/src/main/java/team05/integrated_feed_backend/module/post/repository/PostRepository.java @@ -0,0 +1,11 @@ +package team05.integrated_feed_backend.module.post.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import team05.integrated_feed_backend.module.post.entity.Post; + +@Repository +public interface PostRepository extends JpaRepository { + +} diff --git a/src/main/java/team05/integrated_feed_backend/module/post/service/.gitkeep b/src/main/java/team05/integrated_feed_backend/module/post/service/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/team05/integrated_feed_backend/module/post/service/PostService.java b/src/main/java/team05/integrated_feed_backend/module/post/service/PostService.java new file mode 100644 index 0000000..05fa5b7 --- /dev/null +++ b/src/main/java/team05/integrated_feed_backend/module/post/service/PostService.java @@ -0,0 +1,47 @@ +package team05.integrated_feed_backend.module.post.service; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import lombok.RequiredArgsConstructor; +import team05.integrated_feed_backend.common.code.StatusCode; +import team05.integrated_feed_backend.exception.custom.DataNotFoundException; +import team05.integrated_feed_backend.module.post.entity.Post; +import team05.integrated_feed_backend.module.post.event.publisher.PostEventPublisher; +import team05.integrated_feed_backend.module.post.repository.PostRepository; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class PostService { + + private final PostRepository postRepository; + private final PostEventPublisher postEventPublisher; + + @Transactional + public void increaseLikeCount(Long postId) { + // 게시물 존재 여부 확인 + Post post = postRepository.findById(postId) + .orElseThrow(() -> new DataNotFoundException(StatusCode.POST_NOT_EXIST)); + + // 내부 DB count 올리기 + post.increaseLikeCount(); + + // 이벤트 비동기 발행 + postEventPublisher.publishLikeCountIncreasedEvent(postId, post.getType()); + + } + + @Transactional + public void increaseShareCount(Long postId) { + // 게시물 존재 여부 확인 + Post post = postRepository.findById(postId) + .orElseThrow(() -> new DataNotFoundException(StatusCode.POST_NOT_EXIST)); + + // 내부 db count 올리기 + post.increaseShareCount(); + + // 이벤트 비동기 발행 + postEventPublisher.publishShareCountIncreasedEvent(postId, post.getType()); + } +} diff --git a/src/test/java/team05/integrated_feed_backend/MockEntityFactory.java b/src/test/java/team05/integrated_feed_backend/MockEntityFactory.java new file mode 100644 index 0000000..4c29674 --- /dev/null +++ b/src/test/java/team05/integrated_feed_backend/MockEntityFactory.java @@ -0,0 +1,23 @@ +package team05.integrated_feed_backend; + +import java.util.ArrayList; + +import team05.integrated_feed_backend.common.enums.SocialMediaType; +import team05.integrated_feed_backend.module.post.entity.Post; + +public class MockEntityFactory { + + // Post 생성 + public static Post createMockPost() { + return Post.builder() + .title("Sample Post Title") + .content("This is a sample post content.") + .type(SocialMediaType.FACEBOOK) + .viewCount(100L) + .likeCount(10L) + .shareCount(5L) + .postHashtags(new ArrayList<>()) + .build(); + } + +} diff --git a/src/test/java/team05/integrated_feed_backend/post/PostEventListenerTest.java b/src/test/java/team05/integrated_feed_backend/post/PostEventListenerTest.java new file mode 100644 index 0000000..8452de5 --- /dev/null +++ b/src/test/java/team05/integrated_feed_backend/post/PostEventListenerTest.java @@ -0,0 +1,79 @@ +package team05.integrated_feed_backend.post; + +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +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 team05.integrated_feed_backend.common.enums.SocialMediaType; +import team05.integrated_feed_backend.infra.sns.adapter.FacebookAdapter; +import team05.integrated_feed_backend.infra.sns.adapter.InstagramAdapter; +import team05.integrated_feed_backend.infra.sns.adapter.TwitterAdapter; +import team05.integrated_feed_backend.module.post.event.LikeCountIncreasedEvent; +import team05.integrated_feed_backend.module.post.event.ShareCountIncreasedEvent; +import team05.integrated_feed_backend.module.post.event.listener.PostEventListener; + +@ExtendWith(MockitoExtension.class) +public class PostEventListenerTest { + + @Mock + private FacebookAdapter facebookAdapter; + + @Mock + private TwitterAdapter twitterAdapter; + + @Mock + private InstagramAdapter instagramAdapter; + + @InjectMocks + private PostEventListener postEventListener; + + @Nested + @DisplayName("게시물 좋아요 수 증가 이벤트 처리") + class HandleLikeCountIncreasedEvent { + + @Test + @DisplayName("[성공] LikeCountIncreasedEvent가 수신되어 Instagram API가 호출된다.") + void shouldHandleLikeCountIncreasedEvent() { + // Given + Long postId = 1L; + SocialMediaType type = SocialMediaType.INSTAGRAM; + LikeCountIncreasedEvent event = new LikeCountIncreasedEvent(1L, type); + + // when + postEventListener.handleLikeCountIncreasedEvent(event); + + // then + verify(facebookAdapter, times(0)).increaseLikeCount(postId); + verify(twitterAdapter, times(0)).increaseLikeCount(postId); + verify(instagramAdapter, times(1)).increaseLikeCount(postId); + } + } + + @Nested + @DisplayName("게시물 공유 수 증가 이벤트 처리") + class HandleShareCountIncreasedEvent { + + @Test + @DisplayName("[성공] ShareCountIncreasedEvent가 수신되어 Facebook API가 호출된다.") + void shouldHandleShareCountIncreasedEvent() { + // Given + Long postId = 1L; + SocialMediaType type = SocialMediaType.FACEBOOK; + ShareCountIncreasedEvent event = new ShareCountIncreasedEvent(1L, type); + + // when + postEventListener.handleShareCountIncreasedEvent(event); + + // then + verify(facebookAdapter, times(1)).increaseShareCount(postId); + verify(twitterAdapter, times(0)).increaseShareCount(postId); + verify(instagramAdapter, times(0)).increaseShareCount(postId); + } + } +} \ No newline at end of file diff --git a/src/test/java/team05/integrated_feed_backend/post/PostEventPublisherTest.java b/src/test/java/team05/integrated_feed_backend/post/PostEventPublisherTest.java new file mode 100644 index 0000000..d5a5d0c --- /dev/null +++ b/src/test/java/team05/integrated_feed_backend/post/PostEventPublisherTest.java @@ -0,0 +1,82 @@ +package team05.integrated_feed_backend.post; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.context.ApplicationEventPublisher; + +import team05.integrated_feed_backend.common.enums.SocialMediaType; +import team05.integrated_feed_backend.module.post.event.LikeCountIncreasedEvent; +import team05.integrated_feed_backend.module.post.event.ShareCountIncreasedEvent; +import team05.integrated_feed_backend.module.post.event.publisher.PostEventPublisher; + +@ExtendWith(MockitoExtension.class) +public class PostEventPublisherTest { + + @Mock + private ApplicationEventPublisher eventPublisher; + + @InjectMocks + private PostEventPublisher postEventPublisher; + + @Nested + @DisplayName("LikeCountIncreasedEvent 비동기 발행") + class LikeCountIncreasedEventPublisher { + @Test + @DisplayName("[성공] LikeCountIncreasedEvent가 비동기적으로 발행된다.") + void shouldPublishLikeCountIncreasedEvent() { + // Given + Long postId = 1L; + SocialMediaType type = SocialMediaType.FACEBOOK; + + // when + postEventPublisher.publishLikeCountIncreasedEvent(postId, type); + + // then + // LikeCountIncreasedEvent 생성 및 호출 확인 + ArgumentCaptor captor = ArgumentCaptor.forClass(LikeCountIncreasedEvent.class); + verify(eventPublisher, times(1)).publishEvent(captor.capture()); + + // 캡쳐된 이벤트의 존재 및 해당 postId, SocialType 을 갖고 있는지 확인 + LikeCountIncreasedEvent capturedEvent = captor.getValue(); + assertNotNull(capturedEvent); + assertEquals(postId, capturedEvent.getPostId(), "Post ID should match"); + assertEquals(type, capturedEvent.getType(), "Post Type should match"); + } + } + + @Nested + @DisplayName("ShareCountIncreasedEvent 비동기 발행") + class ShareCountIncreasedEventPublisher { + @Test + @DisplayName("[성공] ShareCountIncreasedEvent가 비동기적으로 발행된다.") + void shouldPublishShareCountIncreasedEvent() { + // Given + Long postId = 1L; + SocialMediaType type = SocialMediaType.FACEBOOK; + + // when + postEventPublisher.publishShareCountIncreasedEvent(postId, type); + + // then + // ShareCountIncreasedEvent 생성 및 호출 확인 + ArgumentCaptor captor = ArgumentCaptor.forClass(ShareCountIncreasedEvent.class); + verify(eventPublisher, times(1)).publishEvent(captor.capture()); + + // 캡쳐된 이벤트의 존재 및 해당 postId, SocialType 을 갖고 있는지 확인 + ShareCountIncreasedEvent capturedEvent = captor.getValue(); + assertNotNull(capturedEvent); + assertEquals(postId, capturedEvent.getPostId(), "Post ID should match"); + assertEquals(type, capturedEvent.getType(), "Post Type should match"); + + } + } +} diff --git a/src/test/java/team05/integrated_feed_backend/post/PostServiceTest.java b/src/test/java/team05/integrated_feed_backend/post/PostServiceTest.java new file mode 100644 index 0000000..dca261e --- /dev/null +++ b/src/test/java/team05/integrated_feed_backend/post/PostServiceTest.java @@ -0,0 +1,102 @@ +package team05.integrated_feed_backend.post; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.BDDMockito.*; + +import java.util.Optional; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +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 team05.integrated_feed_backend.MockEntityFactory; +import team05.integrated_feed_backend.exception.custom.DataNotFoundException; +import team05.integrated_feed_backend.module.post.entity.Post; +import team05.integrated_feed_backend.module.post.event.publisher.PostEventPublisher; +import team05.integrated_feed_backend.module.post.repository.PostRepository; +import team05.integrated_feed_backend.module.post.service.PostService; + +@ExtendWith(MockitoExtension.class) +public class PostServiceTest { + + @Mock + private PostRepository postRepository; + + @Mock + private PostEventPublisher postEventPublisher; + + @InjectMocks + private PostService postService; + + private Post mockPost; + + @BeforeEach + void setUp() { + mockPost = MockEntityFactory.createMockPost(); + } + + @Nested + @DisplayName("게시물 좋아요 수 올리기") + class increaseLikeCount { + + @Test + @DisplayName("[성공] 게시물 좋아요 수를 증가시켰다.") + void shouldIncreaseLikeCountSuccessfully() { + // Given + when(postRepository.findById(mockPost.getPostId())).thenReturn(Optional.of(mockPost)); + + // when + postService.increaseLikeCount(mockPost.getPostId()); + + // then + verify(postRepository, times(1)).findById(mockPost.getPostId()); + verify(postEventPublisher, times(1)).publishLikeCountIncreasedEvent(mockPost.getPostId(), + mockPost.getType()); + assert (mockPost.getLikeCount() == 11L); + + } + + @Test + @DisplayName("[실패] 게시물이 없어 좋아요 수를 증가에 실패했다.") + void shouldThrowExceptionWhenPostNotExists() { + // Given + when(postRepository.findById(mockPost.getPostId())).thenReturn(Optional.empty()); + + // when & then + assertThrows(DataNotFoundException.class, () -> { + postService.increaseLikeCount(mockPost.getPostId()); + }); + + verify(postRepository, times(1)).findById(mockPost.getPostId()); + verify(postEventPublisher, times(0)).publishLikeCountIncreasedEvent(mockPost.getPostId(), + mockPost.getType()); + } + } + + @Nested + @DisplayName("게시물 공유 수 올리기") + class increaseShareCount { + + @Test + @DisplayName("[성공] 게시물 공유 수를 증가시켰다.") + void shouldIncreaseShareCountSuccessfully() { + // Given + when(postRepository.findById(mockPost.getPostId())).thenReturn(Optional.of(mockPost)); + + // when + postService.increaseShareCount(mockPost.getPostId()); + + // then + verify(postRepository, times(1)).findById(mockPost.getPostId()); + verify(postEventPublisher, times(1)).publishShareCountIncreasedEvent(mockPost.getPostId(), + mockPost.getType()); + assert (mockPost.getShareCount() == 6L); + + } + } +}