From d6d2513b1687a993e388f8802daf2def62bb7bf1 Mon Sep 17 00:00:00 2001 From: JunHyeongChoi Date: Wed, 6 Nov 2024 12:57:33 +0900 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20=EC=A2=8B=EC=95=84=EC=9A=94=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EC=9D=91=EB=8B=B5=20=ED=95=84=EB=93=9C?= =?UTF-8?q?=EC=97=90=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/response/FriendDiaryResponse.java | 1 + .../dto/response/MyDiaryResponse.java | 1 + .../repository/LikeRepository.java | 2 ++ .../everymoment/service/DiaryService.java | 18 +++++++++------ .../service/FriendDiaryService.java | 22 ++++++++++--------- .../everymoment/service/LikeService.java | 2 +- 6 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/potatocake/everymoment/dto/response/FriendDiaryResponse.java b/src/main/java/com/potatocake/everymoment/dto/response/FriendDiaryResponse.java index 47ea8da..d533783 100644 --- a/src/main/java/com/potatocake/everymoment/dto/response/FriendDiaryResponse.java +++ b/src/main/java/com/potatocake/everymoment/dto/response/FriendDiaryResponse.java @@ -18,5 +18,6 @@ public class FriendDiaryResponse { private String emoji; private String content; private LikeCountResponse likeCount; + private boolean isLiked; private LocalDateTime createAt; } diff --git a/src/main/java/com/potatocake/everymoment/dto/response/MyDiaryResponse.java b/src/main/java/com/potatocake/everymoment/dto/response/MyDiaryResponse.java index a92bc41..c8c8110 100644 --- a/src/main/java/com/potatocake/everymoment/dto/response/MyDiaryResponse.java +++ b/src/main/java/com/potatocake/everymoment/dto/response/MyDiaryResponse.java @@ -15,5 +15,6 @@ public class MyDiaryResponse { private boolean isBookmark; private String emoji; private String content; + private boolean isLiked; private LocalDateTime createAt; } diff --git a/src/main/java/com/potatocake/everymoment/repository/LikeRepository.java b/src/main/java/com/potatocake/everymoment/repository/LikeRepository.java index dc383b1..367cd00 100644 --- a/src/main/java/com/potatocake/everymoment/repository/LikeRepository.java +++ b/src/main/java/com/potatocake/everymoment/repository/LikeRepository.java @@ -11,4 +11,6 @@ public interface LikeRepository extends JpaRepository { Long countByDiary(Diary diary); + boolean existsByMemberIdAndDiaryId(Long memberId, Long diaryId); + } diff --git a/src/main/java/com/potatocake/everymoment/service/DiaryService.java b/src/main/java/com/potatocake/everymoment/service/DiaryService.java index 12d070c..3378c30 100644 --- a/src/main/java/com/potatocake/everymoment/service/DiaryService.java +++ b/src/main/java/com/potatocake/everymoment/service/DiaryService.java @@ -22,6 +22,7 @@ import com.potatocake.everymoment.repository.DiaryCategoryRepository; import com.potatocake.everymoment.repository.DiaryRepository; import com.potatocake.everymoment.repository.FileRepository; +import com.potatocake.everymoment.repository.LikeRepository; import com.potatocake.everymoment.repository.MemberRepository; import java.time.LocalDate; import java.util.List; @@ -48,6 +49,7 @@ public class DiaryService { private final MemberRepository memberRepository; private final CategoryRepository categoryRepository; private final FileRepository fileRepository; + private final LikeRepository likeRepository; private final GeometryFactory geometryFactory; private final NotificationService notificationService; @@ -138,7 +140,7 @@ public MyDiariesResponse getMyDiaries(Long memberId, DiaryFilterRequest diaryFil Specification spec; - if(!diaryFilterRequest.hasFilter()){ + if (!diaryFilterRequest.hasFilter()) { LocalDate today = LocalDate.now(); spec = DiarySpecification.filterDiaries( @@ -153,9 +155,7 @@ public MyDiariesResponse getMyDiaries(Long memberId, DiaryFilterRequest diaryFil diaryPage = diaryRepository.findAll(spec, PageRequest.of(diaryFilterRequest.getKey(), diaryFilterRequest.getSize())); - } - - else{ + } else { spec = DiarySpecification.filterDiaries( diaryFilterRequest.getKeyword(), emojis, @@ -167,7 +167,8 @@ public MyDiariesResponse getMyDiaries(Long memberId, DiaryFilterRequest diaryFil .and((root, query, builder) -> builder.equal(root.get("member"), currentMember)); diaryPage = diaryRepository.findAll(spec, - PageRequest.of(diaryFilterRequest.getKey(), diaryFilterRequest.getSize(), Sort.by(Sort.Direction.DESC, "createAt"))); + PageRequest.of(diaryFilterRequest.getKey(), diaryFilterRequest.getSize(), + Sort.by(Sort.Direction.DESC, "createAt"))); } List diaryDTOs = diaryPage.getContent().stream() @@ -186,7 +187,7 @@ public MyDiariesResponse getMyDiaries(Long memberId, DiaryFilterRequest diaryFil @Transactional(readOnly = true) public MyDiaryResponse getMyDiary(Long memberId, Long diaryId) { Diary diary = getExistDiary(memberId, diaryId); - return convertToMyDiaryResponseDto(diary); + return convertToMyDiaryResponseDto(diary, memberId); } // 내 일기 위치 조회 @@ -282,7 +283,7 @@ private Diary getExistDiary(Long memberId, Long diaryId) { } //상세 조회시 일기DTO 변환 - private MyDiaryResponse convertToMyDiaryResponseDto(Diary savedDiary) { + private MyDiaryResponse convertToMyDiaryResponseDto(Diary savedDiary, Long memberId) { // 카테고리 찾음 List diaryCategories = diaryCategoryRepository.findByDiary(savedDiary); List categoryResponseList = diaryCategories.stream() @@ -292,6 +293,8 @@ private MyDiaryResponse convertToMyDiaryResponseDto(Diary savedDiary) { .build()) .collect(Collectors.toList()); + boolean isLiked = likeRepository.existsByMemberIdAndDiaryId(memberId, savedDiary.getId()); + return MyDiaryResponse.builder() .id(savedDiary.getId()) .categories(categoryResponseList) @@ -300,6 +303,7 @@ private MyDiaryResponse convertToMyDiaryResponseDto(Diary savedDiary) { .isBookmark(savedDiary.isBookmark()) .emoji(savedDiary.getEmoji()) .content(savedDiary.getContent()) + .isLiked(isLiked) .createAt(savedDiary.getCreateAt()) .build(); } diff --git a/src/main/java/com/potatocake/everymoment/service/FriendDiaryService.java b/src/main/java/com/potatocake/everymoment/service/FriendDiaryService.java index 0d324a9..14daba2 100644 --- a/src/main/java/com/potatocake/everymoment/service/FriendDiaryService.java +++ b/src/main/java/com/potatocake/everymoment/service/FriendDiaryService.java @@ -20,7 +20,6 @@ import com.potatocake.everymoment.repository.FriendRepository; import com.potatocake.everymoment.repository.LikeRepository; import com.potatocake.everymoment.repository.MemberRepository; -import java.time.LocalDate; import java.util.List; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; @@ -59,16 +58,17 @@ public FriendDiariesResponse getFriendDiaries(Long memberId, DiaryFilterRequest List emojis = diaryFilterRequest.getEmojis(); Specification spec = FriendDiarySpecification.filterDiaries( - diaryFilterRequest.getKeyword(), - emojis, - categories, - diaryFilterRequest.getDate(), - diaryFilterRequest.getFrom(), - diaryFilterRequest.getUntil()) - .and((root, query, builder) -> root.get("member").get("id").in(friendIdList)); + diaryFilterRequest.getKeyword(), + emojis, + categories, + diaryFilterRequest.getDate(), + diaryFilterRequest.getFrom(), + diaryFilterRequest.getUntil()) + .and((root, query, builder) -> root.get("member").get("id").in(friendIdList)); diaryPage = diaryRepository.findAll(spec, - PageRequest.of(diaryFilterRequest.getKey(), diaryFilterRequest.getSize(), Sort.by(Sort.Direction.DESC, "createAt"))); + PageRequest.of(diaryFilterRequest.getKey(), diaryFilterRequest.getSize(), + Sort.by(Sort.Direction.DESC, "createAt"))); List friendDiarySimpleResponseList = diaryPage.getContent().stream() .map(this::convertToFriendDiariesResponseDTO) @@ -87,7 +87,7 @@ public FriendDiaryResponse getFriendDiary(Long memberId, Long diaryId) { Diary diary = diaryRepository.findById(diaryId) .orElseThrow(() -> new GlobalException(ErrorCode.DIARY_NOT_FOUND)); - if(!diary.isPublic()){ + if (!diary.isPublic()) { throw new GlobalException(ErrorCode.DIARY_NOT_PUBLIC); } @@ -115,6 +115,7 @@ public FriendDiaryResponse getFriendDiary(Long memberId, Long diaryId) { //like 갯수 반환 Long likeCount = likeRepository.countByDiary(diary); + boolean isLiked = likeRepository.existsByMemberIdAndDiaryId(memberId, diary.getId()); LikeCountResponse count = LikeCountResponse.builder() .likeCount(likeCount) @@ -127,6 +128,7 @@ public FriendDiaryResponse getFriendDiary(Long memberId, Long diaryId) { .emoji(diary.getEmoji()) .content(diary.getContent()) .likeCount(count) + .isLiked(isLiked) .createAt(diary.getCreateAt()) .build(); diff --git a/src/main/java/com/potatocake/everymoment/service/LikeService.java b/src/main/java/com/potatocake/everymoment/service/LikeService.java index 6f9d90f..d2c5586 100644 --- a/src/main/java/com/potatocake/everymoment/service/LikeService.java +++ b/src/main/java/com/potatocake/everymoment/service/LikeService.java @@ -41,7 +41,7 @@ public void toggleLike(Long memberId, Long diaryId) { Diary diary = diaryRepository.findById(diaryId) .orElseThrow(() -> new GlobalException(ErrorCode.DIARY_NOT_FOUND)); - if (!diary.isPublic()) { + if (!diary.isPublic() && !diary.checkOwner(memberId)) { throw new GlobalException(ErrorCode.DIARY_NOT_PUBLIC); } From 4a9b1be97bd08b177b55b9aefd24bd3ea916ce26 Mon Sep 17 00:00:00 2001 From: JunHyeongChoi Date: Wed, 6 Nov 2024 16:53:34 +0900 Subject: [PATCH 2/6] =?UTF-8?q?test:=20Diary=20=EC=BB=A8=ED=8A=B8=EB=A1=A4?= =?UTF-8?q?=EB=9F=AC/=EC=84=9C=EB=B9=84=EC=8A=A4/=EB=A6=AC=ED=8C=8C?= =?UTF-8?q?=EC=A7=80=ED=86=A0=EB=A6=AC/=EB=8F=84=EB=A9=94=EC=9D=B8=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/DiaryController.java | 2 +- .../dto/request/CategoryRequest.java | 4 + .../dto/request/CommentRequest.java | 2 + .../dto/request/DiaryAutoCreateRequest.java | 2 + .../dto/request/DiaryManualCreateRequest.java | 6 + .../dto/request/DiaryPatchRequest.java | 6 + .../controller/DiaryControllerTest.java | 572 ++++++++++++++++++ .../everymoment/entity/DiaryTest.java | 160 +++++ .../repository/DiaryRepositoryTest.java | 201 ++++++ .../everymoment/service/DiaryServiceTest.java | 524 ++++++++++++++++ 10 files changed, 1478 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/potatocake/everymoment/controller/DiaryControllerTest.java create mode 100644 src/test/java/com/potatocake/everymoment/entity/DiaryTest.java create mode 100644 src/test/java/com/potatocake/everymoment/repository/DiaryRepositoryTest.java create mode 100644 src/test/java/com/potatocake/everymoment/service/DiaryServiceTest.java diff --git a/src/main/java/com/potatocake/everymoment/controller/DiaryController.java b/src/main/java/com/potatocake/everymoment/controller/DiaryController.java index 26eb84e..41579ac 100644 --- a/src/main/java/com/potatocake/everymoment/controller/DiaryController.java +++ b/src/main/java/com/potatocake/everymoment/controller/DiaryController.java @@ -72,7 +72,7 @@ public ResponseEntity> createDiaryManual( @Parameter(description = "인증된 사용자 정보", hidden = true) @AuthenticationPrincipal MemberDetails memberDetails, @Parameter(description = "수기 일기 작성 정보", required = true) - @RequestBody DiaryManualCreateRequest diaryManualCreateRequest) { + @RequestBody @Valid DiaryManualCreateRequest diaryManualCreateRequest) { Long memberId = memberDetails.getId(); diaryService.createDiaryManual(memberId, diaryManualCreateRequest); diff --git a/src/main/java/com/potatocake/everymoment/dto/request/CategoryRequest.java b/src/main/java/com/potatocake/everymoment/dto/request/CategoryRequest.java index a397119..cb9b8bf 100644 --- a/src/main/java/com/potatocake/everymoment/dto/request/CategoryRequest.java +++ b/src/main/java/com/potatocake/everymoment/dto/request/CategoryRequest.java @@ -1,7 +1,11 @@ package com.potatocake.everymoment.dto.request; +import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.NoArgsConstructor; +@NoArgsConstructor +@AllArgsConstructor @Getter public class CategoryRequest { private Long categoryId; diff --git a/src/main/java/com/potatocake/everymoment/dto/request/CommentRequest.java b/src/main/java/com/potatocake/everymoment/dto/request/CommentRequest.java index ccb8e47..5798898 100644 --- a/src/main/java/com/potatocake/everymoment/dto/request/CommentRequest.java +++ b/src/main/java/com/potatocake/everymoment/dto/request/CommentRequest.java @@ -3,7 +3,9 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; import lombok.Getter; +import lombok.Setter; +@Setter @Getter public class CommentRequest { diff --git a/src/main/java/com/potatocake/everymoment/dto/request/DiaryAutoCreateRequest.java b/src/main/java/com/potatocake/everymoment/dto/request/DiaryAutoCreateRequest.java index d1dbede..ba6f3c0 100644 --- a/src/main/java/com/potatocake/everymoment/dto/request/DiaryAutoCreateRequest.java +++ b/src/main/java/com/potatocake/everymoment/dto/request/DiaryAutoCreateRequest.java @@ -1,8 +1,10 @@ package com.potatocake.everymoment.dto.request; import com.potatocake.everymoment.dto.LocationPoint; +import lombok.Builder; import lombok.Getter; +@Builder @Getter public class DiaryAutoCreateRequest { private LocationPoint locationPoint; diff --git a/src/main/java/com/potatocake/everymoment/dto/request/DiaryManualCreateRequest.java b/src/main/java/com/potatocake/everymoment/dto/request/DiaryManualCreateRequest.java index 1fd5442..7659c2b 100644 --- a/src/main/java/com/potatocake/everymoment/dto/request/DiaryManualCreateRequest.java +++ b/src/main/java/com/potatocake/everymoment/dto/request/DiaryManualCreateRequest.java @@ -1,10 +1,13 @@ package com.potatocake.everymoment.dto.request; +import com.fasterxml.jackson.annotation.JsonProperty; import com.potatocake.everymoment.dto.LocationPoint; import jakarta.validation.constraints.Size; import java.util.List; +import lombok.Builder; import lombok.Getter; +@Builder @Getter public class DiaryManualCreateRequest { @@ -18,7 +21,10 @@ public class DiaryManualCreateRequest { @Size(max = 250, message = "주소는 250자를 초과할 수 없습니다") private String address; + @JsonProperty("bookmark") private boolean isBookmark; + + @JsonProperty("public") private boolean isPublic; @Size(max = 10, message = "이모지는 10자를 초과할 수 없습니다") diff --git a/src/main/java/com/potatocake/everymoment/dto/request/DiaryPatchRequest.java b/src/main/java/com/potatocake/everymoment/dto/request/DiaryPatchRequest.java index e2071e7..846798b 100644 --- a/src/main/java/com/potatocake/everymoment/dto/request/DiaryPatchRequest.java +++ b/src/main/java/com/potatocake/everymoment/dto/request/DiaryPatchRequest.java @@ -2,8 +2,14 @@ import jakarta.validation.constraints.Size; import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; +@Builder +@NoArgsConstructor +@AllArgsConstructor @Getter public class DiaryPatchRequest { diff --git a/src/test/java/com/potatocake/everymoment/controller/DiaryControllerTest.java b/src/test/java/com/potatocake/everymoment/controller/DiaryControllerTest.java new file mode 100644 index 0000000..c52d79b --- /dev/null +++ b/src/test/java/com/potatocake/everymoment/controller/DiaryControllerTest.java @@ -0,0 +1,572 @@ +package com.potatocake.everymoment.controller; + +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.BDDMockito.any; +import static org.mockito.BDDMockito.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; +import static org.mockito.BDDMockito.willDoNothing; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +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.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.potatocake.everymoment.dto.LocationPoint; +import com.potatocake.everymoment.dto.request.CategoryRequest; +import com.potatocake.everymoment.dto.request.CommentRequest; +import com.potatocake.everymoment.dto.request.DiaryAutoCreateRequest; +import com.potatocake.everymoment.dto.request.DiaryFilterRequest; +import com.potatocake.everymoment.dto.request.DiaryManualCreateRequest; +import com.potatocake.everymoment.dto.request.DiaryPatchRequest; +import com.potatocake.everymoment.dto.response.CommentResponse; +import com.potatocake.everymoment.dto.response.CommentsResponse; +import com.potatocake.everymoment.dto.response.FriendDiariesResponse; +import com.potatocake.everymoment.dto.response.FriendDiaryResponse; +import com.potatocake.everymoment.dto.response.FriendDiarySimpleResponse; +import com.potatocake.everymoment.dto.response.MyDiariesResponse; +import com.potatocake.everymoment.dto.response.MyDiaryResponse; +import com.potatocake.everymoment.dto.response.MyDiarySimpleResponse; +import com.potatocake.everymoment.entity.Member; +import com.potatocake.everymoment.security.MemberDetails; +import com.potatocake.everymoment.service.CommentService; +import com.potatocake.everymoment.service.DiaryService; +import com.potatocake.everymoment.service.FriendDiaryService; +import java.util.List; +import java.util.Objects; +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.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; + +@WithMockUser +@WebMvcTest(DiaryController.class) +class DiaryControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private DiaryService diaryService; + + @MockBean + private FriendDiaryService friendDiaryService; + + @MockBean + private CommentService commentService; + + @Test + @DisplayName("유효한 입력으로 자동 일기가 성공적으로 작성된다.") + void should_CreateAutoEntry_When_ValidInput() throws Exception { + // given + Long memberId = 1L; + Member member = Member.builder() + .id(memberId) + .number(1234L) + .nickname("testUser") + .build(); + MemberDetails memberDetails = new MemberDetails(member); + + DiaryAutoCreateRequest request = DiaryAutoCreateRequest.builder() + .locationPoint(new LocationPoint(37.5665, 126.9780)) + .locationName("Seoul") + .address("Seoul, South Korea") + .build(); + + willDoNothing().given(diaryService).createDiaryAuto(memberId, request); + + // when + ResultActions result = mockMvc.perform(post("/api/diaries/auto") + .with(user(memberDetails)) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.message").value("success")); + + then(diaryService).should().createDiaryAuto( + eq(memberId), + argThat(req -> + Objects.equals(req.getLocationName(), "Seoul") && + Objects.equals(req.getAddress(), "Seoul, South Korea") && + Objects.equals(req.getLocationPoint().getLatitude(), 37.5665) && + Objects.equals(req.getLocationPoint().getLongitude(), 126.978) + ) + ); + } + + @Test + @DisplayName("유효한 입력으로 수동 일기가 성공적으로 작성된다.") + void should_CreateManualEntry_When_ValidInput() throws Exception { + // given + Long memberId = 1L; + Member member = Member.builder() + .id(memberId) + .number(1234L) + .nickname("testUser") + .build(); + MemberDetails memberDetails = new MemberDetails(member); + + LocationPoint locationPoint = new LocationPoint(37.5665, 126.978); + + DiaryManualCreateRequest request = DiaryManualCreateRequest.builder() + .locationPoint(locationPoint) + .locationName("Seoul") + .address("Seoul, South Korea") + .content("Test content") + .emoji("😊") + .categories(List.of(new CategoryRequest(1L))) + .isBookmark(false) + .isPublic(true) + .build(); + + willDoNothing().given(diaryService).createDiaryManual(memberId, request); + + // when + ResultActions result = mockMvc.perform(post("/api/diaries/manual") + .with(user(memberDetails)) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.message").value("success")); + + then(diaryService).should().createDiaryManual( + eq(memberId), + argThat(req -> + Objects.equals(req.getContent(), "Test content") && + Objects.equals(req.getLocationName(), "Seoul") && + Objects.equals(req.getAddress(), "Seoul, South Korea") && + Objects.equals(req.getLocationPoint().getLatitude(), 37.5665) && + Objects.equals(req.getLocationPoint().getLongitude(), 126.978) && + Objects.equals(req.getEmoji(), "😊") && + Objects.equals(req.isPublic(), true) && + Objects.equals(req.isBookmark(), false) && + req.getCategories().size() == 1 && + Objects.equals(req.getCategories().get(0).getCategoryId(), 1L) + ) + ); + } + + @Test + @DisplayName("내 일기 목록이 성공적으로 조회된다.") + void should_ReturnMyDiaries_When_ValidRequest() throws Exception { + // given + Member member = Member.builder() + .id(1L) + .number(1234L) + .nickname("testUser") + .build(); + MemberDetails memberDetails = new MemberDetails(member); + + MyDiariesResponse response = MyDiariesResponse.builder() + .diaries(List.of(MyDiarySimpleResponse.builder() + .id(1L) + .content("Test content") + .locationName("Seoul") + .build())) + .next(null) + .build(); + + given(diaryService.getMyDiaries(eq(member.getId()), any(DiaryFilterRequest.class))) + .willReturn(response); + + // when + ResultActions result = mockMvc.perform(get("/api/diaries/my") + .with(user(memberDetails)) + .param("key", "0") + .param("size", "10")); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.message").value("success")) + .andExpect(jsonPath("$.info.diaries").isArray()); + + then(diaryService).should().getMyDiaries(eq(member.getId()), any(DiaryFilterRequest.class)); + } + + @Test + @DisplayName("내 일기 상세 정보가 성공적으로 조회된다.") + void should_ReturnMyDiaryDetail_When_ValidId() throws Exception { + // given + Long diaryId = 1L; + Member member = Member.builder() + .id(1L) + .number(1234L) + .nickname("testUser") + .build(); + MemberDetails memberDetails = new MemberDetails(member); + + MyDiaryResponse response = MyDiaryResponse.builder() + .id(diaryId) + .content("Test content") + .locationName("Seoul") + .build(); + + given(diaryService.getMyDiary(member.getId(), diaryId)).willReturn(response); + + // when + ResultActions result = mockMvc.perform(get("/api/diaries/my/{diaryId}", diaryId) + .with(user(memberDetails))); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.message").value("success")) + .andExpect(jsonPath("$.info.id").value(diaryId)); + + then(diaryService).should().getMyDiary(member.getId(), diaryId); + } + + @Test + @DisplayName("일기의 위치 정보가 성공적으로 조회된다.") + void should_ReturnDiaryLocation_When_ValidId() throws Exception { + // given + Long diaryId = 1L; + Member member = Member.builder() + .id(1L) + .number(1234L) + .nickname("testUser") + .build(); + MemberDetails memberDetails = new MemberDetails(member); + + LocationPoint locationPoint = new LocationPoint(37.5665, 126.9780); + given(diaryService.getDiaryLocation(member.getId(), diaryId)).willReturn(locationPoint); + + // when + ResultActions result = mockMvc.perform(get("/api/diaries/{diaryId}/location", diaryId) + .with(user(memberDetails))); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.message").value("success")) + .andExpect(jsonPath("$.info.latitude").value(37.5665)) + .andExpect(jsonPath("$.info.longitude").value(126.9780)); + + then(diaryService).should().getDiaryLocation(member.getId(), diaryId); + } + + @Test + @DisplayName("일기가 성공적으로 수정된다.") + void should_UpdateDiary_When_ValidInput() throws Exception { + // given + Long diaryId = 1L; + Member member = Member.builder() + .id(1L) + .number(1234L) + .nickname("testUser") + .build(); + MemberDetails memberDetails = new MemberDetails(member); + + DiaryPatchRequest request = DiaryPatchRequest.builder() + .content("Updated content") + .locationName("Updated location") + .address("Updated address") + .emoji("😊") + .categories(List.of(new CategoryRequest(1L))) + .build(); + + willDoNothing().given(diaryService).updateDiary(member.getId(), diaryId, request); + + // when + ResultActions result = mockMvc.perform(patch("/api/diaries/{diaryId}", diaryId) + .with(user(memberDetails)) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.message").value("success")); + + then(diaryService).should().updateDiary( + eq(member.getId()), + eq(diaryId), + argThat(req -> + Objects.equals(req.getContent(), "Updated content") && + Objects.equals(req.getLocationName(), "Updated location") && + Objects.equals(req.getAddress(), "Updated address") && + Objects.equals(req.getEmoji(), "😊") && + req.getCategories().size() == 1 && + Objects.equals(req.getCategories().get(0).getCategoryId(), 1L)) + ); + } + + @Test + @DisplayName("일기가 성공적으로 삭제된다.") + void should_DeleteDiary_When_ValidId() throws Exception { + // given + Long diaryId = 1L; + Member member = Member.builder() + .id(1L) + .number(1234L) + .nickname("testUser") + .build(); + MemberDetails memberDetails = new MemberDetails(member); + + willDoNothing().given(diaryService).deleteDiary(member.getId(), diaryId); + + // when + ResultActions result = mockMvc.perform(delete("/api/diaries/{diaryId}", diaryId) + .with(user(memberDetails)) + .with(csrf())); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.message").value("success")); + + then(diaryService).should().deleteDiary(member.getId(), diaryId); + } + + @Test + @DisplayName("일기의 북마크 상태가 성공적으로 토글된다.") + void should_ToggleBookmark_When_ValidId() throws Exception { + // given + Long diaryId = 1L; + Member member = Member.builder() + .id(1L) + .number(1234L) + .nickname("testUser") + .build(); + MemberDetails memberDetails = new MemberDetails(member); + + willDoNothing().given(diaryService).toggleBookmark(member.getId(), diaryId); + + // when + ResultActions result = mockMvc.perform(patch("/api/diaries/{diaryId}/bookmark", diaryId) + .with(user(memberDetails)) + .with(csrf())); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.message").value("success")); + + then(diaryService).should().toggleBookmark(member.getId(), diaryId); + } + + @Test + @DisplayName("일기의 공개 상태가 성공적으로 토글된다.") + void should_TogglePrivacy_When_ValidId() throws Exception { + // given + Long diaryId = 1L; + Member member = Member.builder() + .id(1L) + .number(1234L) + .nickname("testUser") + .build(); + MemberDetails memberDetails = new MemberDetails(member); + + willDoNothing().given(diaryService).togglePrivacy(member.getId(), diaryId); + + // when + ResultActions result = mockMvc.perform(patch("/api/diaries/{diaryId}/privacy", diaryId) + .with(user(memberDetails)) + .with(csrf())); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.message").value("success")); + + then(diaryService).should().togglePrivacy(member.getId(), diaryId); + } + + @Test + @DisplayName("친구의 일기 목록이 성공적으로 조회된다.") + void should_ReturnFriendDiaries_When_ValidRequest() throws Exception { + // given + Member member = Member.builder() + .id(1L) + .number(1234L) + .nickname("testUser") + .build(); + MemberDetails memberDetails = new MemberDetails(member); + + FriendDiariesResponse response = FriendDiariesResponse.builder() + .diaries(List.of(FriendDiarySimpleResponse.builder() + .id(1L) + .content("Friend's content") + .locationName("Friend's location") + .build())) + .next(null) + .build(); + + given(friendDiaryService.getFriendDiaries(eq(member.getId()), any(DiaryFilterRequest.class))) + .willReturn(response); + + // when + ResultActions result = mockMvc.perform(get("/api/diaries/friend") + .with(user(memberDetails)) + .param("key", "0") + .param("size", "10")); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.message").value("success")) + .andExpect(jsonPath("$.info.diaries").isArray()); + + then(friendDiaryService).should().getFriendDiaries(eq(member.getId()), any(DiaryFilterRequest.class)); + } + + @Test + @DisplayName("친구의 일기 상세 정보가 성공적으로 조회된다.") + void should_ReturnFriendDiaryDetail_When_ValidId() throws Exception { + // given + Long diaryId = 1L; + Member member = Member.builder() + .id(1L) + .number(1234L) + .nickname("testUser") + .build(); + MemberDetails memberDetails = new MemberDetails(member); + + FriendDiaryResponse response = FriendDiaryResponse.builder() + .id(diaryId) + .content("Friend's content") + .locationName("Friend's location") + .build(); + + given(friendDiaryService.getFriendDiary(member.getId(), diaryId)).willReturn(response); + + // when + ResultActions result = mockMvc.perform(get("/api/diaries/friend/{diaryId}", diaryId) + .with(user(memberDetails))); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.message").value("success")) + .andExpect(jsonPath("$.info.id").value(diaryId)); + + then(friendDiaryService).should().getFriendDiary(member.getId(), diaryId); + } + + @Test + @DisplayName("일기의 댓글 목록이 성공적으로 조회된다.") + void should_ReturnComments_When_ValidDiaryId() throws Exception { + // given + Long diaryId = 1L; + Member member = Member.builder() + .id(1L) + .number(1234L) + .nickname("testUser") + .build(); + MemberDetails memberDetails = new MemberDetails(member); + + CommentsResponse response = CommentsResponse.builder() + .comments(List.of(CommentResponse.builder() + .id(1L) + .content("Test comment") + .build())) + .next(null) + .build(); + + given(commentService.getComments(diaryId, 0, 10)).willReturn(response); + + // when + ResultActions result = mockMvc.perform(get("/api/diaries/{diaryId}/comments", diaryId) + .with(user(memberDetails)) + .param("key", "0") + .param("size", "10")); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.message").value("success")) + .andExpect(jsonPath("$.info.comments").isArray()); + + then(commentService).should().getComments(diaryId, 0, 10); + } + + @Test + @DisplayName("댓글이 성공적으로 작성된다.") + void should_CreateComment_When_ValidInput() throws Exception { + // given + Long diaryId = 1L; + Member member = Member.builder() + .id(1L) + .number(1234L) + .nickname("testUser") + .build(); + MemberDetails memberDetails = new MemberDetails(member); + + CommentRequest request = new CommentRequest(); + request.setContent("Test comment"); + + willDoNothing().given(commentService).createComment(member.getId(), diaryId, request); + + // when + ResultActions result = mockMvc.perform(post("/api/diaries/{diaryId}/comments", diaryId) + .with(user(memberDetails)) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))); + + // then + result.andExpect(status().isOk()) + .andDo(print()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.message").value("success")); + + then(commentService).should().createComment( + eq(member.getId()), + eq(diaryId), + argThat(req -> req.getContent().equals("Test comment")) + ); + } + + @Test + @DisplayName("검증 실패시 수동 일기 작성에 실패한다.") + void should_FailToCreateManualEntry_When_ValidationFails() throws Exception { + // given + Member member = Member.builder() + .id(1L) + .number(1234L) + .nickname("testUser") + .build(); + MemberDetails memberDetails = new MemberDetails(member); + + DiaryManualCreateRequest request = DiaryManualCreateRequest.builder() + .content("x".repeat(15001)) // 최대 길이 초과 + .build(); + + // when + ResultActions result = mockMvc.perform(post("/api/diaries/manual") + .with(user(memberDetails)) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))); + + // then + result.andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(400)); + + then(diaryService).shouldHaveNoInteractions(); + } + +} diff --git a/src/test/java/com/potatocake/everymoment/entity/DiaryTest.java b/src/test/java/com/potatocake/everymoment/entity/DiaryTest.java new file mode 100644 index 0000000..6a2cf8f --- /dev/null +++ b/src/test/java/com/potatocake/everymoment/entity/DiaryTest.java @@ -0,0 +1,160 @@ +package com.potatocake.everymoment.entity; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.locationtech.jts.geom.Point; + +class DiaryTest { + + @Test + @DisplayName("북마크 상태가 성공적으로 토글된다.") + void should_ToggleBookmark_When_Called() { + // given + Diary diary = Diary.builder() + .isBookmark(false) + .build(); + + // when + diary.toggleBookmark(); + + // then + assertThat(diary.isBookmark()).isTrue(); + + // when + diary.toggleBookmark(); + + // then + assertThat(diary.isBookmark()).isFalse(); + } + + @Test + @DisplayName("공개 상태가 성공적으로 토글된다.") + void should_TogglePrivacy_When_Called() { + // given + Diary diary = Diary.builder() + .isPublic(false) + .build(); + + // when + diary.togglePublic(); + + // then + assertThat(diary.isPublic()).isTrue(); + + // when + diary.togglePublic(); + + // then + assertThat(diary.isPublic()).isFalse(); + } + + @Test + @DisplayName("일기 내용이 성공적으로 업데이트된다.") + void should_UpdateContent_When_NewContentProvided() { + // given + Diary diary = Diary.builder() + .content("Original content") + .build(); + String newContent = "Updated content"; + + // when + diary.updateContent(newContent); + + // then + assertThat(diary.getContent()).isEqualTo(newContent); + } + + @Test + @DisplayName("location 정보가 성공적으로 업데이트된다.") + void should_UpdateLocation_When_NewLocationProvided() { + // given + Point originalPoint = mock(Point.class); + Diary diary = Diary.builder() + .locationPoint(originalPoint) + .locationName("Original location") + .address("Original address") + .build(); + + Point newPoint = mock(Point.class); + String newLocationName = "New location"; + String newAddress = "New address"; + + // when + diary.updateLocationPoint(newPoint); + diary.updateLocationName(newLocationName); + diary.updateAddress(newAddress); + + // then + assertThat(diary.getLocationPoint()).isEqualTo(newPoint); + assertThat(diary.getLocationName()).isEqualTo(newLocationName); + assertThat(diary.getAddress()).isEqualTo(newAddress); + } + + @Test + @DisplayName("이모지가 성공적으로 업데이트된다.") + void should_UpdateEmoji_When_NewEmojiProvided() { + // given + Diary diary = Diary.builder() + .emoji("😊") + .build(); + String newEmoji = "😍"; + + // when + diary.updateEmoji(newEmoji); + + // then + assertThat(diary.getEmoji()).isEqualTo(newEmoji); + } + + @Test + @DisplayName("작성자 확인이 성공적으로 수행된다.") + void should_CheckOwner_When_VerifyingOwnership() { + // given + Long memberId = 1L; + Member member = Member.builder() + .id(memberId) + .build(); + + Diary diary = Diary.builder() + .member(member) + .build(); + + // when & then + assertThat(diary.checkOwner(memberId)).isTrue(); + assertThat(diary.checkOwner(2L)).isFalse(); + } + + @Test + @DisplayName("내용이 성공적으로 null로 업데이트된다.") + void should_UpdateContentNull_When_Called() { + // given + Diary diary = Diary.builder() + .content("Original content") + .build(); + + // when + diary.updateContentNull(); + + // then + assertThat(diary.getContent()).isNull(); + } + + @Test + @DisplayName("이모지가 성공적으로 null로 업데이트된다.") + void should_UpdateEmojiNull_When_Called() { + // given + Diary diary = Diary.builder() + .emoji("😊") + .build(); + + // when + diary.updateEmojiNull(); + + // then + assertThat(diary.getEmoji()).isNull(); + } + +} diff --git a/src/test/java/com/potatocake/everymoment/repository/DiaryRepositoryTest.java b/src/test/java/com/potatocake/everymoment/repository/DiaryRepositoryTest.java new file mode 100644 index 0000000..bb2f2b8 --- /dev/null +++ b/src/test/java/com/potatocake/everymoment/repository/DiaryRepositoryTest.java @@ -0,0 +1,201 @@ +package com.potatocake.everymoment.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.potatocake.everymoment.entity.Diary; +import com.potatocake.everymoment.entity.Member; +import com.potatocake.everymoment.service.DiarySpecification; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.GeometryFactory; +import org.locationtech.jts.geom.Point; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.jpa.domain.Specification; + +@DataJpaTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +class DiaryRepositoryTest { + + @Autowired + private DiaryRepository diaryRepository; + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private TestEntityManager entityManager; + + @Test + @DisplayName("일기가 성공적으로 저장된다.") + void should_SaveDiary_When_ValidEntity() { + // given + Member member = createAndSaveMember(); + Diary diary = createDiary(member, "Test content", "Seoul", "Seoul, South Korea"); + + // when + Diary savedDiary = diaryRepository.save(diary); + + // then + assertThat(savedDiary.getId()).isNotNull(); + assertThat(savedDiary.getContent()).isEqualTo("Test content"); + assertThat(savedDiary.getMember()).isEqualTo(member); + assertThat(savedDiary.getAddress()).isEqualTo("Seoul, South Korea"); + } + + @Test + @DisplayName("회원의 일기 목록이 성공적으로 조회된다.") + void should_FindDiaries_When_FilteringByMember() { + // given + Member member = createAndSaveMember(); + + Diary diary1 = createDiary(member, "Content 1", "Seoul", "Address 1"); + Diary diary2 = createDiary(member, "Content 2", "Busan", "Address 2"); + + diaryRepository.saveAll(List.of(diary1, diary2)); + entityManager.flush(); + entityManager.clear(); + + // when + Specification spec = (root, query, builder) -> + builder.equal(root.get("member").get("id"), member.getId()); + Page result = diaryRepository.findAll(spec, PageRequest.of(0, 10)); + + // then + assertThat(result.getContent()).hasSize(2); + assertThat(result.getContent()).extracting("content") + .containsExactly("Content 1", "Content 2"); + } + + @Test + @DisplayName("검색 조건으로 일기가 성공적으로 필터링된다.") + void should_FindDiaries_When_FilteringWithSearchCriteria() { + // given + Member member = createAndSaveMember(); + + Diary diary1 = createDiary(member, "Content Seoul", "Seoul", "Seoul Address"); + diary1 = Diary.builder() + .member(member) + .content("Content Seoul") + .locationPoint(diary1.getLocationPoint()) + .locationName("Seoul") + .address("Seoul Address") + .emoji("😊") + .isPublic(true) + .isBookmark(false) + .build(); + + Diary diary2 = createDiary(member, "Content Busan", "Busan", "Busan Address"); + diary2 = Diary.builder() + .member(member) + .content("Content Busan") + .locationPoint(diary2.getLocationPoint()) + .locationName("Busan") + .address("Busan Address") + .emoji("😍") + .isPublic(true) + .isBookmark(false) + .build(); + + diaryRepository.saveAll(List.of(diary1, diary2)); + entityManager.flush(); + entityManager.clear(); + + // when + Specification spec = DiarySpecification.filterDiaries( + "Seoul", // keyword + List.of("😊"), // emojis + null, // categories + null, // date + null, // from + null, // until + false // isBookmark + ); + + Page result = diaryRepository.findAll(spec, PageRequest.of(0, 10)); + + // then + assertThat(result.getContent()).hasSize(1); + assertThat(result.getContent().get(0).getContent()).isEqualTo("Content Seoul"); + assertThat(result.getContent().get(0).getEmoji()).isEqualTo("😊"); + } + + @Test + @DisplayName("북마크된 일기만 성공적으로 필터링된다.") + void should_FindDiaries_When_FilteringBookmarked() { + // given + Member member = createAndSaveMember(); + + Diary diary1 = createDiary(member, "Content 1", "Seoul", "Seoul Address"); + diary1 = Diary.builder() + .member(member) + .content("Content 1") + .locationPoint(diary1.getLocationPoint()) + .locationName("Seoul") + .address("Seoul Address") + .isBookmark(true) + .isPublic(false) + .build(); + + Diary diary2 = createDiary(member, "Content 2", "Busan", "Busan Address"); + diary2 = Diary.builder() + .member(member) + .content("Content 2") + .locationPoint(diary2.getLocationPoint()) + .locationName("Busan") + .address("Busan Address") + .isBookmark(false) + .isPublic(false) + .build(); + + diaryRepository.saveAll(List.of(diary1, diary2)); + entityManager.flush(); + entityManager.clear(); + + // when + Specification spec = DiarySpecification.filterDiaries( + null, // keyword + null, // emojis + null, // categories + null, // date + null, // from + null, // until + true // isBookmark + ); + + Page result = diaryRepository.findAll(spec, PageRequest.of(0, 10)); + + // then + assertThat(result.getContent()).hasSize(1); + assertThat(result.getContent().get(0).isBookmark()).isTrue(); + } + + private Member createAndSaveMember() { + Member member = Member.builder() + .number(1234L) + .nickname("testUser") + .profileImageUrl("http://example.com/image.jpg") + .build(); + return memberRepository.save(member); + } + + private Diary createDiary(Member member, String content, String locationName, String address) { + Point point = new GeometryFactory().createPoint(new Coordinate(37.5665, 126.978)); + return Diary.builder() + .member(member) + .content(content) + .locationPoint(point) + .locationName(locationName) + .address(address) + .isBookmark(false) + .isPublic(false) + .build(); + } + +} diff --git a/src/test/java/com/potatocake/everymoment/service/DiaryServiceTest.java b/src/test/java/com/potatocake/everymoment/service/DiaryServiceTest.java new file mode 100644 index 0000000..78379fe --- /dev/null +++ b/src/test/java/com/potatocake/everymoment/service/DiaryServiceTest.java @@ -0,0 +1,524 @@ +package com.potatocake.everymoment.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.mock; + +import com.potatocake.everymoment.constant.NotificationType; +import com.potatocake.everymoment.dto.LocationPoint; +import com.potatocake.everymoment.dto.request.CategoryRequest; +import com.potatocake.everymoment.dto.request.DiaryAutoCreateRequest; +import com.potatocake.everymoment.dto.request.DiaryFilterRequest; +import com.potatocake.everymoment.dto.request.DiaryManualCreateRequest; +import com.potatocake.everymoment.dto.request.DiaryPatchRequest; +import com.potatocake.everymoment.dto.response.MyDiariesResponse; +import com.potatocake.everymoment.dto.response.MyDiaryResponse; +import com.potatocake.everymoment.entity.Category; +import com.potatocake.everymoment.entity.Diary; +import com.potatocake.everymoment.entity.DiaryCategory; +import com.potatocake.everymoment.entity.Member; +import com.potatocake.everymoment.exception.ErrorCode; +import com.potatocake.everymoment.exception.GlobalException; +import com.potatocake.everymoment.repository.CategoryRepository; +import com.potatocake.everymoment.repository.DiaryCategoryRepository; +import com.potatocake.everymoment.repository.DiaryRepository; +import com.potatocake.everymoment.repository.FileRepository; +import com.potatocake.everymoment.repository.LikeRepository; +import com.potatocake.everymoment.repository.MemberRepository; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.GeometryFactory; +import org.locationtech.jts.geom.Point; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; + +@ExtendWith(MockitoExtension.class) +class DiaryServiceTest { + + @InjectMocks + private DiaryService diaryService; + + @Mock + private DiaryRepository diaryRepository; + + @Mock + private MemberRepository memberRepository; + + @Mock + private CategoryRepository categoryRepository; + + @Mock + private DiaryCategoryRepository diaryCategoryRepository; + + @Mock + private FileRepository fileRepository; + + @Mock + private LikeRepository likeRepository; + + @Mock + private GeometryFactory geometryFactory; + + @Mock + private NotificationService notificationService; + + @Test + @DisplayName("자동 일기가 성공적으로 저장된다.") + void should_SaveAutoDiary_When_ValidInput() { + // given + Long memberId = 1L; + Member member = Member.builder() + .id(memberId) + .build(); + DiaryAutoCreateRequest request = DiaryAutoCreateRequest.builder() + .locationPoint(new LocationPoint(37.5665, 126.978)) + .locationName("Seoul") + .address("Seoul, South Korea") + .build(); + + Point point = mock(Point.class); + Diary savedDiary = Diary.builder() + .id(1L) + .member(member) + .locationPoint(point) + .locationName("Seoul") + .address("Seoul, South Korea") + .build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(geometryFactory.createPoint(any(Coordinate.class))).willReturn(point); + given(diaryRepository.save(any(Diary.class))).willReturn(savedDiary); + + // when + diaryService.createDiaryAuto(memberId, request); + + // then + then(memberRepository).should().findById(memberId); + then(geometryFactory).should().createPoint(any(Coordinate.class)); + then(diaryRepository).should().save(any(Diary.class)); + then(notificationService).should().createAndSendNotification( + eq(memberId), + eq(NotificationType.MOOD_CHECK), + eq(savedDiary.getId()), + eq(savedDiary.getLocationName()) + ); + } + + @Test + @DisplayName("존재하지 않는 회원이 자동 일기를 작성하려 하면 예외가 발생한다.") + void should_ThrowException_When_MemberNotFoundInAutoCreate() { + // given + Long memberId = 1L; + DiaryAutoCreateRequest request = DiaryAutoCreateRequest.builder() + .locationPoint(new LocationPoint(37.5665, 126.978)) + .locationName("Seoul") + .address("Seoul, South Korea") + .build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> diaryService.createDiaryAuto(memberId, request)) + .isInstanceOf(GlobalException.class) + .hasFieldOrPropertyWithValue("errorCode", ErrorCode.MEMBER_NOT_FOUND); + } + + @Test + @DisplayName("수동 일기가 성공적으로 저장된다.") + void should_SaveManualDiary_When_ValidInput() { + // given + Long memberId = 1L; + Member member = Member.builder() + .id(memberId) + .build(); + + Long categoryId = 1L; + Category category = Category.builder() + .id(categoryId) + .member(member) + .build(); + + DiaryManualCreateRequest request = DiaryManualCreateRequest.builder() + .locationPoint(new LocationPoint(37.5665, 126.978)) + .locationName("Seoul") + .address("Seoul, South Korea") + .content("Test content") + .emoji("😊") + .categories(List.of(new CategoryRequest(categoryId))) + .isBookmark(false) + .isPublic(true) + .build(); + + Point point = mock(Point.class); + Diary savedDiary = Diary.builder() + .id(1L) + .member(member) + .locationPoint(point) + .locationName("Seoul") + .address("Seoul, South Korea") + .content("Test content") + .emoji("😊") + .build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(geometryFactory.createPoint(any(Coordinate.class))).willReturn(point); + given(diaryRepository.save(any(Diary.class))).willReturn(savedDiary); + given(categoryRepository.findById(categoryId)).willReturn(Optional.of(category)); + + // when + diaryService.createDiaryManual(memberId, request); + + // then + then(memberRepository).should().findById(memberId); + then(geometryFactory).should().createPoint(any(Coordinate.class)); + then(diaryRepository).should().save(any(Diary.class)); + then(categoryRepository).should().findById(categoryId); + then(diaryCategoryRepository).should().save(any(DiaryCategory.class)); + } + + @Test + @DisplayName("존재하지 않는 회원이 수동 일기를 작성하려 하면 예외가 발생한다.") + void should_ThrowException_When_MemberNotFoundInManualCreate() { + // given + Long memberId = 1L; + DiaryManualCreateRequest request = DiaryManualCreateRequest.builder() + .locationPoint(new LocationPoint(37.5665, 126.978)) + .locationName("Seoul") + .address("Seoul, South Korea") + .build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> diaryService.createDiaryManual(memberId, request)) + .isInstanceOf(GlobalException.class) + .hasFieldOrPropertyWithValue("errorCode", ErrorCode.MEMBER_NOT_FOUND); + } + + @Test + @DisplayName("존재하지 않는 카테고리로 수동 일기를 작성하려 하면 예외가 발생한다.") + void should_ThrowException_When_CategoryNotFoundInManualCreate() { + // given + Long memberId = 1L; + Member member = Member.builder() + .id(memberId) + .build(); + + Long categoryId = 1L; + DiaryManualCreateRequest request = DiaryManualCreateRequest.builder() + .locationPoint(new LocationPoint(37.5665, 126.978)) + .locationName("Seoul") + .address("Seoul, South Korea") + .categories(List.of(new CategoryRequest(categoryId))) + .build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(categoryRepository.findById(categoryId)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> diaryService.createDiaryManual(memberId, request)) + .isInstanceOf(GlobalException.class) + .hasFieldOrPropertyWithValue("errorCode", ErrorCode.CATEGORY_NOT_FOUND); + } + + @Test + @DisplayName("내 일기 목록이 성공적으로 조회된다.") + void should_ReturnMyDiaries_When_ValidRequest() { + // given + Long memberId = 1L; + Member member = Member.builder() + .id(memberId) + .build(); + + DiaryFilterRequest filterRequest = DiaryFilterRequest.builder() + .key(0) + .size(10) + .build(); + + Diary diary = Diary.builder() + .id(1L) + .member(member) + .content("Test content") + .locationName("Seoul") + .build(); + + Page diaryPage = new PageImpl<>(List.of(diary)); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(diaryRepository.findAll(any(Specification.class), any(Pageable.class))) + .willReturn(diaryPage); + + // when + MyDiariesResponse response = diaryService.getMyDiaries(memberId, filterRequest); + + // then + assertThat(response.getDiaries()).hasSize(1); + assertThat(response.getNext()).isNull(); + then(memberRepository).should().findById(memberId); + then(diaryRepository).should().findAll(any(Specification.class), any(Pageable.class)); + } + + @Test + @DisplayName("내 일기가 성공적으로 조회된다.") + void should_ReturnMyDiary_When_ValidId() { + // given + Long memberId = 1L; + Long diaryId = 1L; + Member member = Member.builder() + .id(memberId) + .build(); + Diary diary = Diary.builder() + .id(diaryId) + .member(member) + .content("Test content") + .locationName("Seoul") + .build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(diaryRepository.findById(diaryId)).willReturn(Optional.of(diary)); + given(diaryCategoryRepository.findByDiary(diary)).willReturn(List.of()); + given(likeRepository.existsByMemberIdAndDiaryId(memberId, diaryId)).willReturn(false); + + // when + MyDiaryResponse response = diaryService.getMyDiary(memberId, diaryId); + + // then + assertThat(response.getId()).isEqualTo(diaryId); + assertThat(response.getContent()).isEqualTo("Test content"); + assertThat(response.getLocationName()).isEqualTo("Seoul"); + then(memberRepository).should().findById(memberId); + then(diaryRepository).should().findById(diaryId); + then(diaryCategoryRepository).should().findByDiary(diary); + then(likeRepository).should().existsByMemberIdAndDiaryId(memberId, diaryId); + } + + @Test + @DisplayName("다른 사용자의 일기를 조회하려고 하면 예외가 발생한다.") + void should_ThrowException_When_NotMyDiary() { + // given + Long memberId = 1L; + Long diaryId = 1L; + Member member = Member.builder() + .id(memberId) + .build(); + Member otherMember = Member.builder() + .id(2L) + .build(); + Diary diary = Diary.builder() + .id(diaryId) + .member(otherMember) + .build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(diaryRepository.findById(diaryId)).willReturn(Optional.of(diary)); + + // when & then + assertThatThrownBy(() -> diaryService.getMyDiary(memberId, diaryId)) + .isInstanceOf(GlobalException.class) + .hasFieldOrPropertyWithValue("errorCode", ErrorCode.DIARY_NOT_FOUND); + } + + @Test + @DisplayName("일기의 위치 정보가 성공적으로 조회된다.") + void should_ReturnLocation_When_ValidId() { + // given + Long memberId = 1L; + Long diaryId = 1L; + Member member = Member.builder() + .id(memberId) + .build(); + + Point point = mock(Point.class); + given(point.getX()).willReturn(37.5665); + given(point.getY()).willReturn(126.978); + + Diary diary = Diary.builder() + .id(diaryId) + .member(member) + .locationPoint(point) + .build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(diaryRepository.findById(diaryId)).willReturn(Optional.of(diary)); + + // when + LocationPoint location = diaryService.getDiaryLocation(memberId, diaryId); + + // then + assertThat(location.getLatitude()).isEqualTo(37.5665); + assertThat(location.getLongitude()).isEqualTo(126.978); + then(memberRepository).should().findById(memberId); + then(diaryRepository).should().findById(diaryId); + } + + @Test + @DisplayName("일기가 성공적으로 수정된다.") + void should_UpdateDiary_When_ValidRequest() { + // given + Long memberId = 1L; + Long diaryId = 1L; + Long categoryId = 1L; + + Member member = Member.builder() + .id(memberId) + .build(); + + Category category = Category.builder() + .id(categoryId) + .member(member) + .build(); + + Diary diary = Diary.builder() + .id(diaryId) + .member(member) + .content("Original content") + .build(); + + DiaryPatchRequest request = DiaryPatchRequest.builder() + .content("Updated content") + .locationName("Updated location") + .categories(List.of(new CategoryRequest(categoryId))) + .build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(diaryRepository.findById(diaryId)).willReturn(Optional.of(diary)); + given(categoryRepository.findById(categoryId)).willReturn(Optional.of(category)); + + // when + diaryService.updateDiary(memberId, diaryId, request); + + // then + assertThat(diary.getContent()).isEqualTo("Updated content"); + assertThat(diary.getLocationName()).isEqualTo("Updated location"); + then(memberRepository).should().findById(memberId); + then(diaryRepository).should().findById(diaryId); + then(diaryCategoryRepository).should().deleteByDiary(diary); + then(categoryRepository).should().findById(categoryId); + then(diaryCategoryRepository).should().save(any(DiaryCategory.class)); + } + + @Test + @DisplayName("일기가 성공적으로 삭제된다.") + void should_DeleteDiary_When_ValidId() { + // given + Long memberId = 1L; + Long diaryId = 1L; + Member member = Member.builder() + .id(memberId) + .build(); + Diary diary = Diary.builder() + .id(diaryId) + .member(member) + .build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(diaryRepository.findById(diaryId)).willReturn(Optional.of(diary)); + + // when + diaryService.deleteDiary(memberId, diaryId); + + // then + then(memberRepository).should().findById(memberId); + then(diaryRepository).should().findById(diaryId); + then(diaryRepository).should().delete(diary); + } + + @Test + @DisplayName("북마크가 성공적으로 토글된다.") + void should_ToggleBookmark_When_ValidId() { + // given + Long memberId = 1L; + Long diaryId = 1L; + Member member = Member.builder() + .id(memberId) + .build(); + Diary diary = Diary.builder() + .id(diaryId) + .member(member) + .isBookmark(false) + .build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(diaryRepository.findById(diaryId)).willReturn(Optional.of(diary)); + + // when + diaryService.toggleBookmark(memberId, diaryId); + + // then + assertThat(diary.isBookmark()).isTrue(); + then(memberRepository).should().findById(memberId); + then(diaryRepository).should().findById(diaryId); + } + + @Test + @DisplayName("공개 상태가 성공적으로 토글된다.") + void should_TogglePrivacy_When_ValidId() { + // given + Long memberId = 1L; + Long diaryId = 1L; + Member member = Member.builder() + .id(memberId) + .build(); + Diary diary = Diary.builder() + .id(diaryId) + .member(member) + .isPublic(false) + .build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(diaryRepository.findById(diaryId)).willReturn(Optional.of(diary)); + + // when + diaryService.togglePrivacy(memberId, diaryId); + + // then + assertThat(diary.isPublic()).isTrue(); + then(memberRepository).should().findById(memberId); + then(diaryRepository).should().findById(diaryId); + } + + @Test + @DisplayName("일기 내용을 삭제하면 null로 설정된다.") + void should_SetContentNull_When_ContentDeleteIsTrue() { + // given + Long memberId = 1L; + Long diaryId = 1L; + Member member = Member.builder() + .id(memberId) + .build(); + Diary diary = Diary.builder() + .id(diaryId) + .member(member) + .content("Original content") + .build(); + + DiaryPatchRequest request = DiaryPatchRequest.builder() + .contentDelete(true) + .build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(diaryRepository.findById(diaryId)).willReturn(Optional.of(diary)); + + // when + diaryService.updateDiary(memberId, diaryId, request); + + // then + assertThat(diary.getContent()).isNull(); + then(memberRepository).should().findById(memberId); + then(diaryRepository).should().findById(diaryId); + } + +} From 4fd36e927f24dea5d7d9a535eb7816e3c2ddb3e4 Mon Sep 17 00:00:00 2001 From: JunHyeongChoi Date: Wed, 6 Nov 2024 17:17:29 +0900 Subject: [PATCH 3/6] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=9A=A9=20db=20=EC=82=AC=EC=9A=A9=ED=95=98=EA=B2=8C=EB=81=94?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-prod.yml | 2 +- .../repository/DiaryRepositoryTest.java | 14 +------------- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 4f5ff81..b03a870 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -12,7 +12,7 @@ spring: jdbc: time_zone: Asia/Seoul hibernate: - ddl-auto: update + ddl-auto: none h2: console: diff --git a/src/test/java/com/potatocake/everymoment/repository/DiaryRepositoryTest.java b/src/test/java/com/potatocake/everymoment/repository/DiaryRepositoryTest.java index bb2f2b8..8694a1e 100644 --- a/src/test/java/com/potatocake/everymoment/repository/DiaryRepositoryTest.java +++ b/src/test/java/com/potatocake/everymoment/repository/DiaryRepositoryTest.java @@ -12,15 +12,12 @@ import org.locationtech.jts.geom.GeometryFactory; import org.locationtech.jts.geom.Point; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; -import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.jpa.domain.Specification; @DataJpaTest -@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) class DiaryRepositoryTest { @Autowired @@ -29,9 +26,6 @@ class DiaryRepositoryTest { @Autowired private MemberRepository memberRepository; - @Autowired - private TestEntityManager entityManager; - @Test @DisplayName("일기가 성공적으로 저장된다.") void should_SaveDiary_When_ValidEntity() { @@ -59,8 +53,6 @@ void should_FindDiaries_When_FilteringByMember() { Diary diary2 = createDiary(member, "Content 2", "Busan", "Address 2"); diaryRepository.saveAll(List.of(diary1, diary2)); - entityManager.flush(); - entityManager.clear(); // when Specification spec = (root, query, builder) -> @@ -104,8 +96,6 @@ void should_FindDiaries_When_FilteringWithSearchCriteria() { .build(); diaryRepository.saveAll(List.of(diary1, diary2)); - entityManager.flush(); - entityManager.clear(); // when Specification spec = DiarySpecification.filterDiaries( @@ -155,8 +145,6 @@ void should_FindDiaries_When_FilteringBookmarked() { .build(); diaryRepository.saveAll(List.of(diary1, diary2)); - entityManager.flush(); - entityManager.clear(); // when Specification spec = DiarySpecification.filterDiaries( @@ -197,5 +185,5 @@ private Diary createDiary(Member member, String content, String locationName, St .isPublic(false) .build(); } - + } From fd72304eae0d900a2874ec932762bef00cdc8bc4 Mon Sep 17 00:00:00 2001 From: JunHyeongChoi Date: Wed, 6 Nov 2024 17:25:02 +0900 Subject: [PATCH 4/6] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EB=A5=BC=20=EC=9C=84=ED=95=9C=20h2=20=ED=99=9C=EC=84=B1?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-prod.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index b03a870..3803e4a 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -16,7 +16,7 @@ spring: h2: console: - enabled: false + enabled: true servlet: multipart: From 587ece18f4782a63145fb8dd3614b0742f11febd Mon Sep 17 00:00:00 2001 From: JunHyeongChoi Date: Wed, 6 Nov 2024 17:33:06 +0900 Subject: [PATCH 5/6] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=ED=95=A0=20=EB=95=8C=EB=A7=8C=20create-drop=20=EB=AA=A8?= =?UTF-8?q?=EB=93=9C=EB=A1=9C=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../potatocake/everymoment/repository/DiaryRepositoryTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/java/com/potatocake/everymoment/repository/DiaryRepositoryTest.java b/src/test/java/com/potatocake/everymoment/repository/DiaryRepositoryTest.java index 8694a1e..551d7a5 100644 --- a/src/test/java/com/potatocake/everymoment/repository/DiaryRepositoryTest.java +++ b/src/test/java/com/potatocake/everymoment/repository/DiaryRepositoryTest.java @@ -16,7 +16,9 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.jpa.domain.Specification; +import org.springframework.test.context.TestPropertySource; +@TestPropertySource(properties = "spring.jpa.hibernate.ddl-auto=create-drop") @DataJpaTest class DiaryRepositoryTest { From 735d4ad1c3587357e1435739ffd88da42f9723d4 Mon Sep 17 00:00:00 2001 From: JunHyeongChoi Date: Wed, 6 Nov 2024 17:45:21 +0900 Subject: [PATCH 6/6] =?UTF-8?q?test:=20db=20=EC=97=90=EB=9F=AC=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-prod.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 3803e4a..9baf0ef 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -3,12 +3,10 @@ spring: url: ${RDS_URL} username: ${RDS_USERNAME} password: ${RDS_PASSWORD} - driver-class-name: com.mysql.cj.jdbc.Driver jpa: properties: hibernate: - dialect: org.hibernate.dialect.MySQLDialect jdbc: time_zone: Asia/Seoul hibernate: