From 4a9b1be97bd08b177b55b9aefd24bd3ea916ce26 Mon Sep 17 00:00:00 2001 From: JunHyeongChoi Date: Wed, 6 Nov 2024 16:53:34 +0900 Subject: [PATCH] =?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); + } + +}