diff --git a/.github/workflows/master_weekly_cicd.yml b/.github/workflows/master_weekly_cicd.yml index 8d38d13..e80fa57 100644 --- a/.github/workflows/master_weekly_cicd.yml +++ b/.github/workflows/master_weekly_cicd.yml @@ -14,6 +14,16 @@ jobs: - name: 프로젝트 코드를 CI 서버로 옮겨오기 uses: actions/checkout@v4 + - name: Gradle 캐시 설정 + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + - name: JDK 21 설치 uses: actions/setup-java@v4 with: @@ -39,8 +49,8 @@ jobs: - name: 빌드로 테스트 수행 및 Jar 파일 생성 run: | chmod +x ./gradlew - ./gradlew clean build - mv build/libs/*SNAPSHOT.jar ./app.jar + ./gradlew clean build --build-cache + - run: mv build/libs/*SNAPSHOT.jar ./app.jar - name: 생성된 Jar 파일 EC2 서버로 전송하기 uses: appleboy/scp-action@v0.1.7 diff --git a/.github/workflows/pr_weekly_ci.yml b/.github/workflows/pr_weekly_ci.yml index 2478d21..78b2a68 100644 --- a/.github/workflows/pr_weekly_ci.yml +++ b/.github/workflows/pr_weekly_ci.yml @@ -17,6 +17,16 @@ jobs: - name: 프로젝트 코드를 CI 서버로 옮겨오기 uses: actions/checkout@v4 + - name: Gradle 캐시 설정 + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + - name: JDK 21 설치 uses: actions/setup-java@v4 with: @@ -42,7 +52,7 @@ jobs: - name: 빌드 테스트 수행 run: | chmod +x ./gradlew - ./gradlew clean build --stacktrace + ./gradlew clean build --build-cache --stacktrace - name: 테스트 수행 결과 보고 uses: EnricoMi/publish-unit-test-result-action@v2 diff --git a/src/main/java/com/potatocake/everymoment/dto/request/CategoryCreateRequest.java b/src/main/java/com/potatocake/everymoment/dto/request/CategoryCreateRequest.java index 3917850..abb9b47 100644 --- a/src/main/java/com/potatocake/everymoment/dto/request/CategoryCreateRequest.java +++ b/src/main/java/com/potatocake/everymoment/dto/request/CategoryCreateRequest.java @@ -4,8 +4,12 @@ import com.potatocake.everymoment.entity.Member; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.NoArgsConstructor; +@NoArgsConstructor +@AllArgsConstructor @Getter public class CategoryCreateRequest { diff --git a/src/main/java/com/potatocake/everymoment/dto/request/FcmTokenRequest.java b/src/main/java/com/potatocake/everymoment/dto/request/FcmTokenRequest.java index 4a9827a..56ad1b9 100644 --- a/src/main/java/com/potatocake/everymoment/dto/request/FcmTokenRequest.java +++ b/src/main/java/com/potatocake/everymoment/dto/request/FcmTokenRequest.java @@ -2,8 +2,12 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.NoArgsConstructor; +@NoArgsConstructor +@AllArgsConstructor @Getter public class FcmTokenRequest { diff --git a/src/main/java/com/potatocake/everymoment/entity/Like.java b/src/main/java/com/potatocake/everymoment/entity/Like.java index 8f8da95..ff8ecfb 100644 --- a/src/main/java/com/potatocake/everymoment/entity/Like.java +++ b/src/main/java/com/potatocake/everymoment/entity/Like.java @@ -9,10 +9,12 @@ import jakarta.persistence.Table; import lombok.AccessLevel; import lombok.Builder; +import lombok.Getter; import lombok.NoArgsConstructor; @Table(name = "likes") @NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter @Entity public class Like { diff --git a/src/test/java/com/potatocake/everymoment/controller/CategoryControllerTest.java b/src/test/java/com/potatocake/everymoment/controller/CategoryControllerTest.java new file mode 100644 index 0000000..f7775ba --- /dev/null +++ b/src/test/java/com/potatocake/everymoment/controller/CategoryControllerTest.java @@ -0,0 +1,218 @@ +package com.potatocake.everymoment.controller; + +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.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.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.potatocake.everymoment.dto.request.CategoryCreateRequest; +import com.potatocake.everymoment.dto.response.CategoryResponse; +import com.potatocake.everymoment.entity.Member; +import com.potatocake.everymoment.security.MemberDetails; +import com.potatocake.everymoment.service.CategoryService; +import java.util.List; +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.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; + +@WebMvcTest(CategoryController.class) +class CategoryControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private CategoryService categoryService; + + @Test + @DisplayName("카테고리 목록이 성공적으로 조회된다.") + void should_ReturnCategories_When_RequestCategories() throws Exception { + // given + Long memberId = 1L; + Member member = Member.builder() + .id(memberId) + .number(1234L) + .nickname("testUser") + .build(); + MemberDetails memberDetails = new MemberDetails(member); + + List responses = List.of( + CategoryResponse.builder() + .id(1L) + .categoryName("Category 1") + .build(), + CategoryResponse.builder() + .id(2L) + .categoryName("Category 2") + .build() + ); + + given(categoryService.getCategories(memberId)).willReturn(responses); + + // when + ResultActions result = mockMvc.perform(get("/api/categories") + .with(user(memberDetails))); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.message").value("success")) + .andExpect(jsonPath("$.info").isArray()) + .andExpect(jsonPath("$.info[0].categoryName").value("Category 1")) + .andExpect(jsonPath("$.info[1].categoryName").value("Category 2")); + + then(categoryService).should().getCategories(memberId); + } + + @Test + @DisplayName("카테고리가 성공적으로 추가된다.") + void should_CreateCategory_When_ValidInput() throws Exception { + // given + Long memberId = 1L; + Member member = Member.builder() + .id(memberId) + .number(1234L) + .nickname("testUser") + .build(); + MemberDetails memberDetails = new MemberDetails(member); + + CategoryCreateRequest request = new CategoryCreateRequest("New Category"); + + willDoNothing().given(categoryService).addCategory( + eq(memberId), + argThat(req -> req.getCategoryName().equals("New Category")) + ); + + // when + ResultActions result = mockMvc.perform(post("/api/categories") + .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(categoryService).should().addCategory( + eq(memberId), + argThat(req -> req.getCategoryName().equals("New Category")) + ); + } + + @Test + @DisplayName("카테고리가 성공적으로 수정된다.") + void should_UpdateCategory_When_ValidInput() throws Exception { + // given + Long memberId = 1L; + Long categoryId = 1L; + Member member = Member.builder() + .id(memberId) + .number(1234L) + .nickname("testUser") + .build(); + MemberDetails memberDetails = new MemberDetails(member); + + CategoryCreateRequest request = new CategoryCreateRequest("Updated Category"); + + willDoNothing().given(categoryService).updateCategory( + eq(categoryId), + eq(memberId), + argThat(req -> req.getCategoryName().equals("Updated Category")) + ); + + // when + ResultActions result = mockMvc.perform(patch("/api/categories/{categoryId}", categoryId) + .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(categoryService).should().updateCategory( + eq(categoryId), + eq(memberId), + argThat(req -> req.getCategoryName().equals("Updated Category")) + ); + } + + @Test + @DisplayName("카테고리가 성공적으로 삭제된다.") + void should_DeleteCategory_When_ValidId() throws Exception { + // given + Long memberId = 1L; + Long categoryId = 1L; + Member member = Member.builder() + .id(memberId) + .number(1234L) + .nickname("testUser") + .build(); + MemberDetails memberDetails = new MemberDetails(member); + + willDoNothing().given(categoryService).deleteCategory(categoryId, memberId); + + // when + ResultActions result = mockMvc.perform(delete("/api/categories/{categoryId}", categoryId) + .with(user(memberDetails)) + .with(csrf())); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.message").value("success")); + + then(categoryService).should().deleteCategory(categoryId, memberId); + } + + @Test + @DisplayName("카테고리명이 누락되면 생성에 실패한다.") + void should_FailToCreate_When_CategoryNameMissing() throws Exception { + // given + Long memberId = 1L; + Member member = Member.builder() + .id(memberId) + .number(1234L) + .nickname("testUser") + .build(); + MemberDetails memberDetails = new MemberDetails(member); + + CategoryCreateRequest request = new CategoryCreateRequest(); // categoryName 누락 + + // when + ResultActions result = mockMvc.perform(post("/api/categories") + .with(user(memberDetails)) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))); + + // then + result.andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(400)); + + then(categoryService).shouldHaveNoInteractions(); + } + +} diff --git a/src/test/java/com/potatocake/everymoment/controller/CommentControllerTest.java b/src/test/java/com/potatocake/everymoment/controller/CommentControllerTest.java new file mode 100644 index 0000000..7130077 --- /dev/null +++ b/src/test/java/com/potatocake/everymoment/controller/CommentControllerTest.java @@ -0,0 +1,139 @@ +package com.potatocake.everymoment.controller; + +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +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.patch; +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.request.CommentRequest; +import com.potatocake.everymoment.entity.Member; +import com.potatocake.everymoment.security.MemberDetails; +import com.potatocake.everymoment.service.CommentService; +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.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; + +@WebMvcTest(CommentController.class) +class CommentControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private CommentService commentService; + + @Test + @DisplayName("댓글이 성공적으로 수정된다.") + void should_UpdateComment_When_ValidInput() throws Exception { + // given + Long memberId = 1L; + Long commentId = 1L; + Member member = Member.builder() + .id(memberId) + .number(1234L) + .nickname("testUser") + .build(); + MemberDetails memberDetails = new MemberDetails(member); + + CommentRequest request = new CommentRequest(); + request.setContent("Updated comment"); + + willDoNothing().given(commentService).updateComment( + eq(memberId), + eq(commentId), + argThat(req -> req.getContent().equals("Updated comment")) + ); + + // when + ResultActions result = mockMvc.perform(patch("/api/comments/{commentId}", commentId) + .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(commentService).should().updateComment( + eq(memberId), + eq(commentId), + argThat(req -> req.getContent().equals("Updated comment")) + ); + } + + @Test + @DisplayName("댓글이 성공적으로 삭제된다.") + void should_DeleteComment_When_ValidId() throws Exception { + // given + Long memberId = 1L; + Long commentId = 1L; + Member member = Member.builder() + .id(memberId) + .number(1234L) + .nickname("testUser") + .build(); + MemberDetails memberDetails = new MemberDetails(member); + + willDoNothing().given(commentService).deleteComment(memberId, commentId); + + // when + ResultActions result = mockMvc.perform(delete("/api/comments/{commentId}", commentId) + .with(user(memberDetails)) + .with(csrf())); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.message").value("success")); + + then(commentService).should().deleteComment(memberId, commentId); + } + + @Test + @DisplayName("댓글 내용이 누락되면 수정에 실패한다.") + void should_FailToUpdate_When_ContentMissing() throws Exception { + // given + Long memberId = 1L; + Long commentId = 1L; + Member member = Member.builder() + .id(memberId) + .number(1234L) + .nickname("testUser") + .build(); + MemberDetails memberDetails = new MemberDetails(member); + + CommentRequest request = new CommentRequest(); + // content 누락 + + // when + ResultActions result = mockMvc.perform(patch("/api/comments/{commentId}", commentId) + .with(user(memberDetails)) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))); + + // then + result.andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(400)); + + then(commentService).shouldHaveNoInteractions(); + } + +} diff --git a/src/test/java/com/potatocake/everymoment/controller/FcmControllerTest.java b/src/test/java/com/potatocake/everymoment/controller/FcmControllerTest.java new file mode 100644 index 0000000..8a9d8d2 --- /dev/null +++ b/src/test/java/com/potatocake/everymoment/controller/FcmControllerTest.java @@ -0,0 +1,126 @@ +package com.potatocake.everymoment.controller; + +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.post; +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.request.FcmTokenRequest; +import com.potatocake.everymoment.entity.Member; +import com.potatocake.everymoment.security.MemberDetails; +import com.potatocake.everymoment.service.FcmService; +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.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; + +@WebMvcTest(FcmController.class) +class FcmControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private FcmService fcmService; + + @Test + @DisplayName("FCM 토큰이 성공적으로 등록된다.") + void should_RegisterToken_When_ValidInput() throws Exception { + // given + Long memberId = 1L; + Member member = Member.builder() + .id(memberId) + .number(1234L) + .nickname("testUser") + .build(); + MemberDetails memberDetails = new MemberDetails(member); + + FcmTokenRequest request = new FcmTokenRequest("fcm-token-123", "device123"); + + willDoNothing().given(fcmService).registerToken(memberId, "device123", "fcm-token-123"); + + // when + ResultActions result = mockMvc.perform(post("/api/fcm/token") + .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(fcmService).should().registerToken(memberId, "device123", "fcm-token-123"); + } + + @Test + @DisplayName("FCM 토큰이 성공적으로 삭제된다.") + void should_RemoveToken_When_ValidInput() throws Exception { + // given + Long memberId = 1L; + Member member = Member.builder() + .id(memberId) + .number(1234L) + .nickname("testUser") + .build(); + MemberDetails memberDetails = new MemberDetails(member); + String deviceId = "device123"; + + willDoNothing().given(fcmService).removeToken(memberId, deviceId); + + // when + ResultActions result = mockMvc.perform(delete("/api/fcm/token") + .with(user(memberDetails)) + .with(csrf()) + .param("deviceId", deviceId)); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.message").value("success")); + + then(fcmService).should().removeToken(memberId, deviceId); + } + + @Test + @DisplayName("FCM 토큰 요청 데이터가 누락되면 등록에 실패한다.") + void should_FailToRegister_When_InvalidInput() throws Exception { + // given + Long memberId = 1L; + Member member = Member.builder() + .id(memberId) + .number(1234L) + .nickname("testUser") + .build(); + MemberDetails memberDetails = new MemberDetails(member); + + FcmTokenRequest request = new FcmTokenRequest(); // deviceId와 fcmToken 누락 + + // when + ResultActions result = mockMvc.perform(post("/api/fcm/token") + .with(user(memberDetails)) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))); + + // then + result.andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(400)); + + then(fcmService).shouldHaveNoInteractions(); + } + +} diff --git a/src/test/java/com/potatocake/everymoment/controller/FileControllerTest.java b/src/test/java/com/potatocake/everymoment/controller/FileControllerTest.java new file mode 100644 index 0000000..e0daf22 --- /dev/null +++ b/src/test/java/com/potatocake/everymoment/controller/FileControllerTest.java @@ -0,0 +1,156 @@ +package com.potatocake.everymoment.controller; + +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.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.potatocake.everymoment.dto.response.FileResponse; +import com.potatocake.everymoment.entity.Member; +import com.potatocake.everymoment.security.MemberDetails; +import com.potatocake.everymoment.service.FileService; +import java.util.List; +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.mock.web.MockMultipartFile; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; + +@WithMockUser +@WebMvcTest(FileController.class) +class FileControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private FileService fileService; + + @Test + @DisplayName("파일 목록이 성공적으로 조회된다.") + void should_ReturnFiles_When_ValidDiaryId() throws Exception { + // given + Long diaryId = 1L; + List responses = List.of( + FileResponse.builder() + .id(1L) + .imageUrl("https://example.com/image1.jpg") + .order(1) + .build(), + FileResponse.builder() + .id(2L) + .imageUrl("https://example.com/image2.jpg") + .order(2) + .build() + ); + + given(fileService.getFiles(diaryId)).willReturn(responses); + + // when + ResultActions result = mockMvc.perform(get("/api/diaries/{diaryId}/files", diaryId)); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.message").value("success")) + .andExpect(jsonPath("$.info").isArray()) + .andExpect(jsonPath("$.info[0].imageUrl").value("https://example.com/image1.jpg")) + .andExpect(jsonPath("$.info[1].imageUrl").value("https://example.com/image2.jpg")); + + then(fileService).should().getFiles(diaryId); + } + + @Test + @DisplayName("파일이 성공적으로 업로드된다.") + void should_UploadFiles_When_ValidInput() throws Exception { + // given + Long diaryId = 1L; + Long memberId = 1L; + Member member = Member.builder() + .id(memberId) + .number(1234L) + .nickname("testUser") + .build(); + MemberDetails memberDetails = new MemberDetails(member); + + MockMultipartFile file1 = new MockMultipartFile( + "files", + "test1.jpg", + MediaType.IMAGE_JPEG_VALUE, + "test image 1".getBytes() + ); + MockMultipartFile file2 = new MockMultipartFile( + "files", + "test2.jpg", + MediaType.IMAGE_JPEG_VALUE, + "test image 2".getBytes() + ); + + willDoNothing().given(fileService).uploadFiles(diaryId, memberId, List.of(file1, file2)); + + // when + ResultActions result = mockMvc.perform(multipart("/api/diaries/{diaryId}/files", diaryId) + .file(file1) + .file(file2) + .with(user(memberDetails)) + .with(csrf())); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.message").value("success")); + + then(fileService).should().uploadFiles(diaryId, memberId, List.of(file1, file2)); + } + + @Test + @DisplayName("파일이 성공적으로 수정된다.") + void should_UpdateFiles_When_ValidInput() throws Exception { + // given + Long diaryId = 1L; + Long memberId = 1L; + Member member = Member.builder() + .id(memberId) + .number(1234L) + .nickname("testUser") + .build(); + MemberDetails memberDetails = new MemberDetails(member); + + MockMultipartFile file = new MockMultipartFile( + "files", + "test.jpg", + MediaType.IMAGE_JPEG_VALUE, + "test image".getBytes() + ); + + willDoNothing().given(fileService).updateFiles(diaryId, memberId, List.of(file)); + + // when + ResultActions result = mockMvc.perform(multipart("/api/diaries/{diaryId}/files", diaryId) + .file(file) + .with(request -> { + request.setMethod("PUT"); + return request; + }) + .with(user(memberDetails)) + .with(csrf())); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.message").value("success")); + + then(fileService).should().updateFiles(diaryId, memberId, List.of(file)); + } + +} diff --git a/src/test/java/com/potatocake/everymoment/controller/FriendControllerTest.java b/src/test/java/com/potatocake/everymoment/controller/FriendControllerTest.java new file mode 100644 index 0000000..a28679a --- /dev/null +++ b/src/test/java/com/potatocake/everymoment/controller/FriendControllerTest.java @@ -0,0 +1,149 @@ +package com.potatocake.everymoment.controller; + +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.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.potatocake.everymoment.dto.response.FriendDiarySimpleResponse; +import com.potatocake.everymoment.dto.response.FriendListResponse; +import com.potatocake.everymoment.dto.response.FriendProfileResponse; +import com.potatocake.everymoment.dto.response.OneFriendDiariesResponse; +import com.potatocake.everymoment.entity.Member; +import com.potatocake.everymoment.security.MemberDetails; +import com.potatocake.everymoment.service.FriendService; +import java.util.List; +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.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; + +@WebMvcTest(FriendController.class) +class FriendControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private FriendService friendService; + + @Test + @DisplayName("특정 친구의 일기 목록이 성공적으로 조회된다.") + void should_ReturnFriendDiaries_When_ValidRequest() throws Exception { + // given + Long memberId = 1L; + Long friendId = 2L; + Member member = Member.builder() + .id(memberId) + .number(1234L) + .nickname("testUser") + .build(); + MemberDetails memberDetails = new MemberDetails(member); + + OneFriendDiariesResponse response = OneFriendDiariesResponse.builder() + .diaries(List.of( + FriendDiarySimpleResponse.builder() + .id(1L) + .content("Test diary") + .locationName("Test location") + .build() + )) + .next(null) + .build(); + + given(friendService.OneFriendDiariesResponse(memberId, friendId, null, 0, 10)) + .willReturn(response); + + // when + ResultActions result = mockMvc.perform(get("/api/friends/{friendId}/diaries", friendId) + .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[0].content").value("Test diary")); + + then(friendService).should().OneFriendDiariesResponse(memberId, friendId, null, 0, 10); + } + + @Test + @DisplayName("내 친구 목록이 성공적으로 조회된다.") + void should_ReturnFriendList_When_ValidRequest() throws Exception { + // given + Long memberId = 1L; + Member member = Member.builder() + .id(memberId) + .number(1234L) + .nickname("testUser") + .build(); + MemberDetails memberDetails = new MemberDetails(member); + + FriendListResponse response = FriendListResponse.builder() + .friends(List.of( + FriendProfileResponse.builder() + .id(2L) + .nickname("friend") + .profileImageUrl("https://example.com/profile.jpg") + .build() + )) + .next(null) + .build(); + + given(friendService.getFriendList(memberId, null, 0, 10)) + .willReturn(response); + + // when + ResultActions result = mockMvc.perform(get("/api/friends/friends") + .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.friends[0].nickname").value("friend")); + + then(friendService).should().getFriendList(memberId, null, 0, 10); + } + + @Test + @DisplayName("친구가 성공적으로 삭제된다.") + void should_DeleteFriend_When_ValidId() throws Exception { + // given + Long memberId = 1L; + Long friendId = 2L; + Member member = Member.builder() + .id(memberId) + .number(1234L) + .nickname("testUser") + .build(); + MemberDetails memberDetails = new MemberDetails(member); + + willDoNothing().given(friendService).deleteFriend(memberId, friendId); + + // when + ResultActions result = mockMvc.perform(delete("/api/friends/{friendId}", friendId) + .with(user(memberDetails)) + .with(csrf())); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.message").value("success")); + + then(friendService).should().deleteFriend(memberId, friendId); + } + +} diff --git a/src/test/java/com/potatocake/everymoment/controller/FriendRequestControllerTest.java b/src/test/java/com/potatocake/everymoment/controller/FriendRequestControllerTest.java new file mode 100644 index 0000000..a800405 --- /dev/null +++ b/src/test/java/com/potatocake/everymoment/controller/FriendRequestControllerTest.java @@ -0,0 +1,188 @@ +package com.potatocake.everymoment.controller; + +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.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.potatocake.everymoment.dto.response.FriendRequestPageRequest; +import com.potatocake.everymoment.dto.response.FriendRequestResponse; +import com.potatocake.everymoment.entity.Member; +import com.potatocake.everymoment.exception.ErrorCode; +import com.potatocake.everymoment.security.MemberDetails; +import com.potatocake.everymoment.service.FriendRequestService; +import java.util.List; +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.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; + +@WebMvcTest(FriendRequestController.class) +class FriendRequestControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private FriendRequestService friendRequestService; + + @Test + @DisplayName("친구 요청 목록이 성공적으로 조회된다.") + void should_ReturnFriendRequests_When_ValidRequest() throws Exception { + // given + Long memberId = 1L; + Member member = Member.builder() + .id(memberId) + .number(1234L) + .nickname("testUser") + .build(); + MemberDetails memberDetails = new MemberDetails(member); + + FriendRequestPageRequest response = FriendRequestPageRequest.builder() + .friendRequests(List.of( + FriendRequestResponse.builder() + .id(1L) + .senderId(2L) + .nickname("requester") + .profileImageUrl("https://example.com/profile.jpg") + .build() + )) + .next(null) + .build(); + + given(friendRequestService.getFriendRequests(null, 10, memberId)) + .willReturn(response); + + // when + ResultActions result = mockMvc.perform(get("/api/friend-requests") + .with(user(memberDetails)) + .param("size", "10")); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.message").value("success")) + .andExpect(jsonPath("$.info.friendRequests[0].nickname").value("requester")); + + then(friendRequestService).should().getFriendRequests(null, 10, memberId); + } + + @Test + @DisplayName("친구 요청이 성공적으로 전송된다.") + void should_SendFriendRequest_When_ValidRequest() throws Exception { + // given + Long memberId = 1L; + Long targetMemberId = 2L; + Member member = Member.builder() + .id(memberId) + .number(1234L) + .nickname("testUser") + .build(); + MemberDetails memberDetails = new MemberDetails(member); + + willDoNothing().given(friendRequestService).sendFriendRequest(memberId, targetMemberId); + + // when + ResultActions result = mockMvc.perform(post("/api/members/{memberId}/friend-requests", targetMemberId) + .with(user(memberDetails)) + .with(csrf())); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.message").value("success")); + + then(friendRequestService).should().sendFriendRequest(memberId, targetMemberId); + } + + @Test + @DisplayName("자신에게 친구 요청을 보내면 실패한다.") + void should_FailToSendRequest_When_RequestingSelf() throws Exception { + // given + Long memberId = 1L; + Member member = Member.builder() + .id(memberId) + .number(1234L) + .nickname("testUser") + .build(); + MemberDetails memberDetails = new MemberDetails(member); + + // when + ResultActions result = mockMvc.perform(post("/api/members/{memberId}/friend-requests", memberId) + .with(user(memberDetails)) + .with(csrf())); + + // then + result.andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(400)) + .andExpect(jsonPath("$.message").value(ErrorCode.SELF_FRIEND_REQUEST.getMessage())); + + then(friendRequestService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("친구 요청이 성공적으로 수락된다.") + void should_AcceptFriendRequest_When_ValidRequest() throws Exception { + // given + Long memberId = 1L; + Long requestId = 1L; + Member member = Member.builder() + .id(memberId) + .number(1234L) + .nickname("testUser") + .build(); + MemberDetails memberDetails = new MemberDetails(member); + + willDoNothing().given(friendRequestService).acceptFriendRequest(requestId, memberId); + + // when + ResultActions result = mockMvc.perform(post("/api/friend-requests/{requestId}/accept", requestId) + .with(user(memberDetails)) + .with(csrf())); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.message").value("success")); + + then(friendRequestService).should().acceptFriendRequest(requestId, memberId); + } + + @Test + @DisplayName("친구 요청이 성공적으로 거절된다.") + void should_RejectFriendRequest_When_ValidRequest() throws Exception { + // given + Long memberId = 1L; + Long requestId = 1L; + Member member = Member.builder() + .id(memberId) + .number(1234L) + .nickname("testUser") + .build(); + MemberDetails memberDetails = new MemberDetails(member); + + willDoNothing().given(friendRequestService).rejectFriendRequest(requestId, memberId); + + // when + ResultActions result = mockMvc.perform(delete("/api/friend-requests/{requestId}/reject", requestId) + .with(user(memberDetails)) + .with(csrf())); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.message").value("success")); + + then(friendRequestService).should().rejectFriendRequest(requestId, memberId); + } + +} diff --git a/src/test/java/com/potatocake/everymoment/controller/LikeControllerTest.java b/src/test/java/com/potatocake/everymoment/controller/LikeControllerTest.java new file mode 100644 index 0000000..8476f1e --- /dev/null +++ b/src/test/java/com/potatocake/everymoment/controller/LikeControllerTest.java @@ -0,0 +1,87 @@ +package com.potatocake.everymoment.controller; + +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.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.potatocake.everymoment.dto.response.LikeCountResponse; +import com.potatocake.everymoment.entity.Member; +import com.potatocake.everymoment.security.MemberDetails; +import com.potatocake.everymoment.service.LikeService; +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.security.test.context.support.WithMockUser; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; + +@WithMockUser +@WebMvcTest(LikeController.class) +class LikeControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private LikeService likeService; + + @Test + @DisplayName("좋아요 수가 성공적으로 조회된다.") + void should_ReturnLikeCount_When_ValidDiaryId() throws Exception { + // given + Long diaryId = 1L; + LikeCountResponse response = LikeCountResponse.builder() + .likeCount(5L) + .build(); + + given(likeService.getLikeCount(diaryId)).willReturn(response); + + // when + ResultActions result = mockMvc.perform(get("/api/diaries/{diaryId}/likes", diaryId)); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.message").value("success")) + .andExpect(jsonPath("$.info.likeCount").value(5)); + + then(likeService).should().getLikeCount(diaryId); + } + + @Test + @DisplayName("좋아요가 성공적으로 토글된다.") + void should_ToggleLike_When_ValidRequest() throws Exception { + // given + Long diaryId = 1L; + Long memberId = 1L; + Member member = Member.builder() + .id(memberId) + .number(1234L) + .nickname("testUser") + .build(); + MemberDetails memberDetails = new MemberDetails(member); + + willDoNothing().given(likeService).toggleLike(memberId, diaryId); + + // when + ResultActions result = mockMvc.perform(post("/api/diaries/{diaryId}/likes", diaryId) + .with(user(memberDetails)) + .with(csrf())); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.message").value("success")); + + then(likeService).should().toggleLike(memberId, diaryId); + } + +} diff --git a/src/test/java/com/potatocake/everymoment/controller/MemberControllerTest.java b/src/test/java/com/potatocake/everymoment/controller/MemberControllerTest.java index d24b883..4e8f2f7 100644 --- a/src/test/java/com/potatocake/everymoment/controller/MemberControllerTest.java +++ b/src/test/java/com/potatocake/everymoment/controller/MemberControllerTest.java @@ -1,36 +1,38 @@ package com.potatocake.everymoment.controller; -import static com.potatocake.everymoment.exception.ErrorCode.INFO_REQUIRED; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.anyString; 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.multipart; -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.response.AnonymousLoginResponse; +import com.potatocake.everymoment.dto.response.FriendRequestStatus; import com.potatocake.everymoment.dto.response.MemberDetailResponse; import com.potatocake.everymoment.dto.response.MemberMyResponse; import com.potatocake.everymoment.dto.response.MemberSearchResponse; +import com.potatocake.everymoment.dto.response.MemberSearchResultResponse; import com.potatocake.everymoment.entity.Member; +import com.potatocake.everymoment.exception.ErrorCode; import com.potatocake.everymoment.security.MemberDetails; import com.potatocake.everymoment.service.MemberService; +import java.util.List; 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.mock.web.MockMultipartFile; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; -import org.springframework.web.multipart.MultipartFile; @WithMockUser @WebMvcTest(MemberController.class) @@ -39,103 +41,169 @@ class MemberControllerTest { @Autowired private MockMvc mockMvc; + @Autowired + private ObjectMapper objectMapper; + @MockBean private MemberService memberService; @Test - @DisplayName("회원 목록 검색이 성공적으로 수행된다.") - void should_SearchMembers_When_ValidInput() throws Exception { + @DisplayName("익명 로그인이 성공적으로 수행된다.") + void should_LoginAnonymously_When_ValidRequest() throws Exception { // given - String nickname = "testUser"; - Long key = 1L; - int size = 10; + Long memberNumber = null; + AnonymousLoginResponse response = AnonymousLoginResponse.builder() + .number(1234L) + .token("jwt-token") + .build(); - Long memberId = 1L; - MemberDetails memberDetails = createMemberDetails(memberId, 1234L, "test"); + given(memberService.anonymousLogin(memberNumber)).willReturn(response); + + // when + ResultActions result = mockMvc.perform(get("/api/members/anonymous-login")); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.message").value("success")) + .andExpect(jsonPath("$.info.number").value(1234)) + .andExpect(jsonPath("$.info.token").value("jwt-token")); + + then(memberService).should().anonymousLogin(memberNumber); + } - MemberSearchResponse response = MemberSearchResponse.builder().build(); + @Test + @DisplayName("회원 검색이 성공적으로 수행된다.") + void should_SearchMembers_When_ValidRequest() throws Exception { + // given + Long memberId = 1L; + Member member = Member.builder() + .id(memberId) + .number(1234L) + .nickname("testUser") + .build(); + MemberDetails memberDetails = new MemberDetails(member); + + MemberSearchResponse response = MemberSearchResponse.builder() + .members(List.of( + MemberSearchResultResponse.builder() + .id(2L) + .nickname("searchResult") + .profileImageUrl("https://example.com/profile.jpg") + .friendRequestStatus(FriendRequestStatus.NONE) + .build() + )) + .next(null) + .build(); - given(memberService.searchMembers(nickname, key, size, memberId)).willReturn(response); + given(memberService.searchMembers(null, null, 10, memberId)) + .willReturn(response); // when ResultActions result = mockMvc.perform(get("/api/members") .with(user(memberDetails)) - .param("nickname", nickname) - .param("key", key.toString()) - .param("size", String.valueOf(size))); + .param("size", "10")); // then - result - .andExpect(status().isOk()) + result.andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(200)) - .andExpect(jsonPath("$.message").value("success")); + .andExpect(jsonPath("$.message").value("success")) + .andExpect(jsonPath("$.info.members[0].nickname").value("searchResult")); - then(memberService).should().searchMembers(nickname, key, size, memberId); + then(memberService).should().searchMembers(null, null, 10, memberId); } @Test - @DisplayName("내 정보 조회가 성공적으로 수행된다.") - void should_ReturnMyInfo_When_ValidMember() throws Exception { + @DisplayName("내 정보가 성공적으로 조회된다.") + void should_ReturnMyInfo_When_ValidRequest() throws Exception { // given Long memberId = 1L; - MemberDetails memberDetails = createMemberDetails(memberId, 1234L, "test"); + Member member = Member.builder() + .id(memberId) + .number(1234L) + .nickname("testUser") + .build(); + MemberDetails memberDetails = new MemberDetails(member); + + MemberDetailResponse response = MemberDetailResponse.builder() + .id(memberId) + .nickname("testUser") + .profileImageUrl("https://example.com/profile.jpg") + .build(); - MemberDetailResponse response = MemberDetailResponse.builder().build(); given(memberService.getMyInfo(memberId)).willReturn(response); // when - ResultActions result = performGet("/api/members/me", memberDetails); + ResultActions result = mockMvc.perform(get("/api/members/me") + .with(user(memberDetails))); // then - result - .andExpect(status().isOk()) + result.andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(200)) .andExpect(jsonPath("$.message").value("success")) - .andExpect(jsonPath("$.info").isNotEmpty()); + .andExpect(jsonPath("$.info.nickname").value("testUser")); then(memberService).should().getMyInfo(memberId); } @Test - @DisplayName("회원 ID로 정보 조회가 성공적으로 수행된다.") - void should_ReturnMemberInfo_When_ValidMemberId() throws Exception { + @DisplayName("회원 정보가 성공적으로 조회된다.") + void should_ReturnMemberInfo_When_ValidId() throws Exception { // given - Long memberId = 1L; - MemberMyResponse response = MemberMyResponse.builder().build(); + Long targetMemberId = 2L; + MemberMyResponse response = MemberMyResponse.builder() + .id(targetMemberId) + .nickname("otherUser") + .profileImageUrl("https://example.com/profile.jpg") + .build(); - given(memberService.getMemberInfo(memberId)).willReturn(response); + given(memberService.getMemberInfo(targetMemberId)).willReturn(response); // when - ResultActions result = mockMvc.perform(get("/api/members/{memberId}", memberId)); + ResultActions result = mockMvc.perform(get("/api/members/{memberId}", targetMemberId)); // then - result - .andExpect(status().isOk()) + result.andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(200)) .andExpect(jsonPath("$.message").value("success")) - .andExpect(jsonPath("$.info").isNotEmpty()); + .andExpect(jsonPath("$.info.nickname").value("otherUser")); - then(memberService).should().getMemberInfo(memberId); + then(memberService).should().getMemberInfo(targetMemberId); } @Test - @DisplayName("회원 정보 수정이 성공적으로 수행된다.") + @DisplayName("회원 정보가 성공적으로 수정된다.") void should_UpdateMemberInfo_When_ValidInput() throws Exception { // given Long memberId = 1L; - MemberDetails memberDetails = createMemberDetails(memberId, 1234L, "test"); + Member member = Member.builder() + .id(memberId) + .number(1234L) + .nickname("testUser") + .build(); + MemberDetails memberDetails = new MemberDetails(member); + + MockMultipartFile profileImage = new MockMultipartFile( + "profileImage", + "profile.jpg", + MediaType.IMAGE_JPEG_VALUE, + "test image".getBytes() + ); - MockMultipartFile profileImage = new MockMultipartFile("profileImage", "image.png", "image/png", new byte[]{}); String nickname = "newNickname"; - willDoNothing().given(memberService).updateMemberInfo(anyLong(), any(MultipartFile.class), anyString()); + willDoNothing().given(memberService) + .updateMemberInfo(memberId, profileImage, nickname); // when - ResultActions result = performMultipart("/api/members", profileImage, nickname, memberDetails); + ResultActions result = mockMvc.perform(multipart("/api/members") + .file(profileImage) + .param("nickname", nickname) + .with(user(memberDetails)) + .with(csrf())); // then - result - .andExpect(status().isOk()) + result.andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(200)) .andExpect(jsonPath("$.message").value("success")); @@ -143,47 +211,55 @@ void should_UpdateMemberInfo_When_ValidInput() throws Exception { } @Test - @DisplayName("프로필 이미지와 닉네임이 모두 누락되면 예외가 발생한다.") - void should_ThrowException_When_ProfileImageAndNicknameAreMissing() throws Exception { + @DisplayName("회원이 성공적으로 삭제된다.") + void should_DeleteMember_When_ValidRequest() throws Exception { + // given + Long memberId = 1L; + Member member = Member.builder() + .id(memberId) + .number(1234L) + .nickname("testUser") + .build(); + MemberDetails memberDetails = new MemberDetails(member); + + willDoNothing().given(memberService).deleteMember(memberId); + // when - ResultActions result = mockMvc.perform(multipart("/api/members") + ResultActions result = mockMvc.perform(delete("/api/members") + .with(user(memberDetails)) .with(csrf())); // then - result - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.code").value(INFO_REQUIRED.getStatus().value())) - .andExpect(jsonPath("$.message").value(INFO_REQUIRED.getMessage())) - .andDo(print()); + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.message").value("success")); - then(memberService).shouldHaveNoInteractions(); + then(memberService).should().deleteMember(memberId); } - private Member createMember(Long memberId, Long number, String nickname) { - return Member.builder() + @Test + @DisplayName("정보가 누락되면 회원 정보 수정에 실패한다.") + void should_FailToUpdate_When_InfoMissing() throws Exception { + // given + Long memberId = 1L; + Member member = Member.builder() .id(memberId) - .number(number) - .nickname(nickname) + .number(1234L) + .nickname("testUser") .build(); - } - - private MemberDetails createMemberDetails(Long memberId, Long number, String nickname) { - Member member = createMember(memberId, number, nickname); - return new MemberDetails(member); - } - - private ResultActions performGet(String url, MemberDetails memberDetails) throws Exception { - return mockMvc.perform(get(url) - .with(user(memberDetails))); - } + MemberDetails memberDetails = new MemberDetails(member); - private ResultActions performMultipart(String url, MockMultipartFile file, String nickname, - MemberDetails memberDetails) throws Exception { - return mockMvc.perform(multipart(url) - .file(file) - .param("nickname", nickname) + // when + ResultActions result = mockMvc.perform(multipart("/api/members") .with(user(memberDetails)) .with(csrf())); + + // then + result.andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(400)) + .andExpect(jsonPath("$.message").value(ErrorCode.INFO_REQUIRED.getMessage())); + + then(memberService).shouldHaveNoInteractions(); } } diff --git a/src/test/java/com/potatocake/everymoment/entity/CategoryTest.java b/src/test/java/com/potatocake/everymoment/entity/CategoryTest.java new file mode 100644 index 0000000..22330b6 --- /dev/null +++ b/src/test/java/com/potatocake/everymoment/entity/CategoryTest.java @@ -0,0 +1,51 @@ +package com.potatocake.everymoment.entity; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.potatocake.everymoment.exception.ErrorCode; +import com.potatocake.everymoment.exception.GlobalException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class CategoryTest { + + @Test + @DisplayName("카테고리명이 성공적으로 업데이트된다.") + void should_UpdateName_When_NewNameProvided() { + // given + Category category = Category.builder() + .categoryName("Original Name") + .build(); + + // when + category.update("New Name"); + + // then + assertThat(category.getCategoryName()).isEqualTo("New Name"); + } + + @Test + @DisplayName("소유자 확인이 성공적으로 수행된다.") + void should_CheckOwner_When_VerifyingOwnership() { + // given + Long ownerId = 1L; + Member owner = Member.builder() + .id(ownerId) + .build(); + + Category category = Category.builder() + .member(owner) + .build(); + + // when & then + assertThatCode(() -> category.checkOwner(ownerId)) + .doesNotThrowAnyException(); + + assertThatThrownBy(() -> category.checkOwner(2L)) + .isInstanceOf(GlobalException.class) + .hasFieldOrPropertyWithValue("errorCode", ErrorCode.CATEGORY_NOT_OWNER); + } + +} diff --git a/src/test/java/com/potatocake/everymoment/entity/CommentTest.java b/src/test/java/com/potatocake/everymoment/entity/CommentTest.java new file mode 100644 index 0000000..1a9b07c --- /dev/null +++ b/src/test/java/com/potatocake/everymoment/entity/CommentTest.java @@ -0,0 +1,41 @@ +package com.potatocake.everymoment.entity; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class CommentTest { + + @Test + @DisplayName("댓글 내용이 성공적으로 업데이트된다.") + void should_UpdateContent_When_NewContentProvided() { + // given + Comment comment = Comment.builder() + .content("Original content") + .build(); + String newContent = "Updated content"; + + // when + comment.updateContent(newContent); + + // then + assertThat(comment.getContent()).isEqualTo(newContent); + } + + @Test + @DisplayName("null 내용으로 업데이트하면 기존 내용이 유지된다.") + void should_KeepOriginalContent_When_NullContentProvided() { + // given + Comment comment = Comment.builder() + .content("Original content") + .build(); + + // when + comment.updateContent(null); + + // then + assertThat(comment.getContent()).isEqualTo("Original content"); + } + +} diff --git a/src/test/java/com/potatocake/everymoment/entity/DeviceTokenTest.java b/src/test/java/com/potatocake/everymoment/entity/DeviceTokenTest.java new file mode 100644 index 0000000..ff5ab34 --- /dev/null +++ b/src/test/java/com/potatocake/everymoment/entity/DeviceTokenTest.java @@ -0,0 +1,27 @@ +package com.potatocake.everymoment.entity; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class DeviceTokenTest { + + @DisplayName("토큰값이 성공적으로 업데이트된다.") + @Test + void should_UpdateToken_When_NewTokenProvided() { + // given + DeviceToken deviceToken = DeviceToken.builder() + .fcmToken("old-token") + .deviceId("device123") + .build(); + String newToken = "new-token"; + + // when + deviceToken.updateToken(newToken); + + // then + assertThat(deviceToken.getFcmToken()).isEqualTo(newToken); + } + +} diff --git a/src/test/java/com/potatocake/everymoment/entity/FileTest.java b/src/test/java/com/potatocake/everymoment/entity/FileTest.java new file mode 100644 index 0000000..e6bfe21 --- /dev/null +++ b/src/test/java/com/potatocake/everymoment/entity/FileTest.java @@ -0,0 +1,35 @@ +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; + +class FileTest { + + @DisplayName("파일이 성공적으로 생성된다.") + @Test + void should_CreateFile_When_ValidInput() { + // given + Long id = 1L; + Diary diary = mock(Diary.class); + String imageUrl = "https://example.com/image.jpg"; + Integer order = 1; + + // when + File file = File.builder() + .id(id) + .diary(diary) + .imageUrl(imageUrl) + .order(order) + .build(); + + // then + assertThat(file.getId()).isEqualTo(id); + assertThat(file.getDiary()).isEqualTo(diary); + assertThat(file.getImageUrl()).isEqualTo(imageUrl); + assertThat(file.getOrder()).isEqualTo(order); + } + +} diff --git a/src/test/java/com/potatocake/everymoment/entity/FriendRequestTest.java b/src/test/java/com/potatocake/everymoment/entity/FriendRequestTest.java new file mode 100644 index 0000000..83531ff --- /dev/null +++ b/src/test/java/com/potatocake/everymoment/entity/FriendRequestTest.java @@ -0,0 +1,34 @@ +package com.potatocake.everymoment.entity; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class FriendRequestTest { + + @Test + @DisplayName("친구 요청이 성공적으로 생성된다.") + void should_CreateFriendRequest_When_ValidInput() { + // given + Member sender = Member.builder() + .id(1L) + .nickname("sender") + .build(); + Member receiver = Member.builder() + .id(2L) + .nickname("receiver") + .build(); + + // when + FriendRequest request = FriendRequest.builder() + .sender(sender) + .receiver(receiver) + .build(); + + // then + assertThat(request.getSender()).isEqualTo(sender); + assertThat(request.getReceiver()).isEqualTo(receiver); + } + +} diff --git a/src/test/java/com/potatocake/everymoment/entity/FriendTest.java b/src/test/java/com/potatocake/everymoment/entity/FriendTest.java new file mode 100644 index 0000000..badb6a6 --- /dev/null +++ b/src/test/java/com/potatocake/everymoment/entity/FriendTest.java @@ -0,0 +1,34 @@ +package com.potatocake.everymoment.entity; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class FriendTest { + + @DisplayName("친구 관계가 성공적으로 생성된다.") + @Test + void should_CreateFriendship_When_ValidInput() { + // given + Member member = Member.builder() + .id(1L) + .nickname("user") + .build(); + Member friend = Member.builder() + .id(2L) + .nickname("friend") + .build(); + + // when + Friend friendship = Friend.builder() + .member(member) + .friend(friend) + .build(); + + // then + assertThat(friendship.getMember()).isEqualTo(member); + assertThat(friendship.getFriend()).isEqualTo(friend); + } + +} diff --git a/src/test/java/com/potatocake/everymoment/entity/LikeTest.java b/src/test/java/com/potatocake/everymoment/entity/LikeTest.java new file mode 100644 index 0000000..c3a664c --- /dev/null +++ b/src/test/java/com/potatocake/everymoment/entity/LikeTest.java @@ -0,0 +1,34 @@ +package com.potatocake.everymoment.entity; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class LikeTest { + + @Test + @DisplayName("좋아요가 성공적으로 생성된다.") + void should_CreateLike_When_ValidInput() { + // given + Member member = Member.builder() + .id(1L) + .nickname("user") + .build(); + Diary diary = Diary.builder() + .id(1L) + .content("Test diary") + .build(); + + // when + Like like = Like.builder() + .member(member) + .diary(diary) + .build(); + + // then + assertThat(like.getMember()).isEqualTo(member); + assertThat(like.getDiary()).isEqualTo(diary); + } + +} diff --git a/src/test/java/com/potatocake/everymoment/entity/MemberTest.java b/src/test/java/com/potatocake/everymoment/entity/MemberTest.java new file mode 100644 index 0000000..de6bb1a --- /dev/null +++ b/src/test/java/com/potatocake/everymoment/entity/MemberTest.java @@ -0,0 +1,86 @@ +package com.potatocake.everymoment.entity; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class MemberTest { + + @Test + @DisplayName("회원 정보가 성공적으로 생성된다.") + void should_CreateMember_When_ValidInput() { + // given + Long id = 1L; + Long number = 1234L; + String nickname = "testUser"; + String profileImageUrl = "https://example.com/profile.jpg"; + + // when + Member member = Member.builder() + .id(id) + .number(number) + .nickname(nickname) + .profileImageUrl(profileImageUrl) + .build(); + + // then + assertThat(member.getId()).isEqualTo(id); + assertThat(member.getNumber()).isEqualTo(number); + assertThat(member.getNickname()).isEqualTo(nickname); + assertThat(member.getProfileImageUrl()).isEqualTo(profileImageUrl); + assertThat(member.isDeleted()).isFalse(); + } + + @Test + @DisplayName("회원 정보가 성공적으로 업데이트된다.") + void should_UpdateInfo_When_NewValuesProvided() { + // given + Member member = Member.builder() + .nickname("oldNickname") + .profileImageUrl("https://example.com/old.jpg") + .build(); + + // when + member.update("newNickname", "https://example.com/new.jpg"); + + // then + assertThat(member.getNickname()).isEqualTo("newNickname"); + assertThat(member.getProfileImageUrl()).isEqualTo("https://example.com/new.jpg"); + } + + @Test + @DisplayName("닉네임만 업데이트된다.") + void should_UpdateNickname_When_OnlyNicknameProvided() { + // given + Member member = Member.builder() + .nickname("oldNickname") + .profileImageUrl("https://example.com/profile.jpg") + .build(); + + // when + member.update("newNickname", null); + + // then + assertThat(member.getNickname()).isEqualTo("newNickname"); + assertThat(member.getProfileImageUrl()).isEqualTo("https://example.com/profile.jpg"); + } + + @Test + @DisplayName("프로필 이미지만 업데이트된다.") + void should_UpdateProfileImage_When_OnlyProfileImageProvided() { + // given + Member member = Member.builder() + .nickname("testUser") + .profileImageUrl("https://example.com/old.jpg") + .build(); + + // when + member.update(null, "https://example.com/new.jpg"); + + // then + assertThat(member.getNickname()).isEqualTo("testUser"); + assertThat(member.getProfileImageUrl()).isEqualTo("https://example.com/new.jpg"); + } + +} diff --git a/src/test/java/com/potatocake/everymoment/repository/CategoryRepositoryTest.java b/src/test/java/com/potatocake/everymoment/repository/CategoryRepositoryTest.java new file mode 100644 index 0000000..34bb2ff --- /dev/null +++ b/src/test/java/com/potatocake/everymoment/repository/CategoryRepositoryTest.java @@ -0,0 +1,79 @@ +package com.potatocake.everymoment.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.potatocake.everymoment.entity.Category; +import com.potatocake.everymoment.entity.Member; +import java.util.List; +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.orm.jpa.DataJpaTest; +import org.springframework.test.context.TestPropertySource; + +@TestPropertySource(properties = "spring.jpa.hibernate.ddl-auto=create-drop") +@DataJpaTest +class CategoryRepositoryTest { + + @Autowired + private CategoryRepository categoryRepository; + + @Autowired + private MemberRepository memberRepository; + + private Member createAndSaveMember() { + Member member = Member.builder() + .number(1234L) + .nickname("testUser") + .profileImageUrl("https://example.com/image.jpg") + .build(); + return memberRepository.save(member); + } + + @Test + @DisplayName("카테고리가 성공적으로 저장된다.") + void should_SaveCategory_When_ValidEntity() { + // given + Member member = createAndSaveMember(); + Category category = Category.builder() + .member(member) + .categoryName("Test Category") + .build(); + + // when + Category savedCategory = categoryRepository.save(category); + + // then + assertThat(savedCategory.getId()).isNotNull(); + assertThat(savedCategory.getCategoryName()).isEqualTo("Test Category"); + assertThat(savedCategory.getMember()).isEqualTo(member); + } + + @Test + @DisplayName("회원의 카테고리 목록이 성공적으로 조회된다.") + void should_FindCategories_When_FilteringByMemberId() { + // given + Member member = createAndSaveMember(); + + Category category1 = Category.builder() + .member(member) + .categoryName("Category 1") + .build(); + + Category category2 = Category.builder() + .member(member) + .categoryName("Category 2") + .build(); + + categoryRepository.saveAll(List.of(category1, category2)); + + // when + List categories = categoryRepository.findByMemberId(member.getId()); + + // then + assertThat(categories).hasSize(2); + assertThat(categories).extracting("categoryName") + .containsExactlyInAnyOrder("Category 1", "Category 2"); + } + +} diff --git a/src/test/java/com/potatocake/everymoment/repository/CommentRepositoryTest.java b/src/test/java/com/potatocake/everymoment/repository/CommentRepositoryTest.java new file mode 100644 index 0000000..c2ec107 --- /dev/null +++ b/src/test/java/com/potatocake/everymoment/repository/CommentRepositoryTest.java @@ -0,0 +1,240 @@ +package com.potatocake.everymoment.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.potatocake.everymoment.entity.Comment; +import com.potatocake.everymoment.entity.Diary; +import com.potatocake.everymoment.entity.Member; +import java.util.List; +import java.util.Optional; +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.orm.jpa.DataJpaTest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.test.context.TestPropertySource; + +@TestPropertySource(properties = "spring.jpa.hibernate.ddl-auto=create-drop") +@DataJpaTest +class CommentRepositoryTest { + + @Autowired + private CommentRepository commentRepository; + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private DiaryRepository diaryRepository; + + @Test + @DisplayName("댓글이 성공적으로 저장된다.") + void should_SaveComment_When_ValidEntity() { + // given + Member member = createAndSaveMember(); + Diary diary = createAndSaveDiary(member); + + Comment comment = Comment.builder() + .member(member) + .diary(diary) + .content("Test comment") + .build(); + + // when + Comment savedComment = commentRepository.save(comment); + + // then + assertThat(savedComment.getId()).isNotNull(); + assertThat(savedComment.getContent()).isEqualTo("Test comment"); + assertThat(savedComment.getMember()).isEqualTo(member); + assertThat(savedComment.getDiary()).isEqualTo(diary); + } + + @Test + @DisplayName("일기의 댓글 목록이 성공적으로 조회된다.") + void should_FindComments_When_FilteringByDiaryId() { + // given + Member member = createAndSaveMember(); + Diary diary = createAndSaveDiary(member); + + Comment comment1 = Comment.builder() + .member(member) + .diary(diary) + .content("Comment 1") + .build(); + + Comment comment2 = Comment.builder() + .member(member) + .diary(diary) + .content("Comment 2") + .build(); + + commentRepository.saveAll(List.of(comment1, comment2)); + + // when + Page comments = commentRepository.findAllByDiaryId( + diary.getId(), + PageRequest.of(0, 10) + ); + + // then + assertThat(comments.getContent()).hasSize(2); + assertThat(comments.getContent()) + .extracting("content") + .containsExactly("Comment 1", "Comment 2"); + } + + @Test + @DisplayName("페이징이 성공적으로 동작한다.") + void should_ReturnPagedResult_When_UsingPagination() { + // given + Member member = createAndSaveMember(); + Diary diary = createAndSaveDiary(member); + + List comments = List.of( + Comment.builder() + .member(member) + .diary(diary) + .content("Comment 1") + .build(), + Comment.builder() + .member(member) + .diary(diary) + .content("Comment 2") + .build(), + Comment.builder() + .member(member) + .diary(diary) + .content("Comment 3") + .build() + ); + + commentRepository.saveAll(comments); + + // when + Page firstPage = commentRepository.findAllByDiaryId( + diary.getId(), + PageRequest.of(0, 2) + ); + + Page secondPage = commentRepository.findAllByDiaryId( + diary.getId(), + PageRequest.of(1, 2) + ); + + // then + assertThat(firstPage.getContent()).hasSize(2); + assertThat(firstPage.hasNext()).isTrue(); + assertThat(firstPage.getContent()) + .extracting("content") + .containsExactly("Comment 1", "Comment 2"); + + assertThat(secondPage.getContent()).hasSize(1); + assertThat(secondPage.hasNext()).isFalse(); + assertThat(secondPage.getContent()) + .extracting("content") + .containsExactly("Comment 3"); + } + + @Test + @DisplayName("다른 일기의 댓글은 조회되지 않는다.") + void should_NotFindComments_When_DifferentDiary() { + // given + Member member = createAndSaveMember(); + Diary diary1 = createAndSaveDiary(member); + Diary diary2 = createAndSaveDiary(member); + + Comment comment1 = Comment.builder() + .member(member) + .diary(diary1) + .content("Comment for diary 1") + .build(); + + Comment comment2 = Comment.builder() + .member(member) + .diary(diary2) + .content("Comment for diary 2") + .build(); + + commentRepository.saveAll(List.of(comment1, comment2)); + + // when + Page comments = commentRepository.findAllByDiaryId( + diary1.getId(), + PageRequest.of(0, 10) + ); + + // then + assertThat(comments.getContent()).hasSize(1); + assertThat(comments.getContent()) + .extracting("content") + .containsExactly("Comment for diary 1"); + } + + @Test + @DisplayName("빈 페이지가 성공적으로 반환된다.") + void should_ReturnEmptyPage_When_NoComments() { + // given + Member member = createAndSaveMember(); + Diary diary = createAndSaveDiary(member); + + // when + Page comments = commentRepository.findAllByDiaryId( + diary.getId(), + PageRequest.of(0, 10) + ); + + // then + assertThat(comments.getContent()).isEmpty(); + assertThat(comments.getTotalElements()).isZero(); + assertThat(comments.hasNext()).isFalse(); + } + + @Test + @DisplayName("댓글이 성공적으로 삭제된다.") + void should_DeleteComment_When_ValidEntity() { + // given + Member member = createAndSaveMember(); + Diary diary = createAndSaveDiary(member); + Comment comment = Comment.builder() + .member(member) + .diary(diary) + .content("Test comment") + .build(); + Comment savedComment = commentRepository.save(comment); + + // when + commentRepository.delete(savedComment); + + // then + Optional foundComment = commentRepository.findById(savedComment.getId()); + assertThat(foundComment).isEmpty(); + } + + private Member createAndSaveMember() { + Member member = Member.builder() + .number(1234L) + .nickname("testUser") + .profileImageUrl("https://example.com/image.jpg") + .build(); + return memberRepository.save(member); + } + + private Diary createAndSaveDiary(Member member) { + Point point = new GeometryFactory().createPoint(new Coordinate(37.5665, 126.978)); + + Diary diary = Diary.builder() + .member(member) + .content("Test diary") + .locationName("Test location") + .address("Test address") + .locationPoint(point) + .build(); + return diaryRepository.save(diary); + } + +} diff --git a/src/test/java/com/potatocake/everymoment/repository/DeviceTokenRepositoryTest.java b/src/test/java/com/potatocake/everymoment/repository/DeviceTokenRepositoryTest.java new file mode 100644 index 0000000..99ee770 --- /dev/null +++ b/src/test/java/com/potatocake/everymoment/repository/DeviceTokenRepositoryTest.java @@ -0,0 +1,210 @@ +package com.potatocake.everymoment.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.potatocake.everymoment.entity.DeviceToken; +import com.potatocake.everymoment.entity.Member; +import java.util.List; +import java.util.Optional; +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.orm.jpa.DataJpaTest; +import org.springframework.test.context.TestPropertySource; + +@TestPropertySource(properties = "spring.jpa.hibernate.ddl-auto=create-drop") +@DataJpaTest +class DeviceTokenRepositoryTest { + + @Autowired + private DeviceTokenRepository deviceTokenRepository; + + @Autowired + private MemberRepository memberRepository; + + private Member createAndSaveMember() { + Member member = Member.builder() + .number(1234L) + .nickname("testUser") + .profileImageUrl("https://example.com/image.jpg") + .build(); + return memberRepository.save(member); + } + + @Test + @DisplayName("디바이스 토큰이 성공적으로 저장된다.") + void should_SaveToken_When_ValidEntity() { + // given + Member member = createAndSaveMember(); + DeviceToken token = DeviceToken.builder() + .member(member) + .deviceId("device123") + .fcmToken("fcm-token-123") + .build(); + + // when + DeviceToken savedToken = deviceTokenRepository.save(token); + + // then + assertThat(savedToken.getId()).isNotNull(); + assertThat(savedToken.getDeviceId()).isEqualTo("device123"); + assertThat(savedToken.getFcmToken()).isEqualTo("fcm-token-123"); + assertThat(savedToken.getMember()).isEqualTo(member); + } + + @Test + @DisplayName("회원의 모든 디바이스 토큰이 조회된다.") + void should_FindAllTokens_When_FilteringByMemberId() { + // given + Member member = createAndSaveMember(); + DeviceToken token1 = DeviceToken.builder() + .member(member) + .deviceId("device1") + .fcmToken("token1") + .build(); + DeviceToken token2 = DeviceToken.builder() + .member(member) + .deviceId("device2") + .fcmToken("token2") + .build(); + + deviceTokenRepository.saveAll(List.of(token1, token2)); + + // when + List tokens = deviceTokenRepository.findAllByMemberId(member.getId()); + + // then + assertThat(tokens).hasSize(2); + assertThat(tokens).extracting("deviceId") + .containsExactlyInAnyOrder("device1", "device2"); + } + + @Test + @DisplayName("특정 디바이스의 토큰이 조회된다.") + void should_FindToken_When_FilteringByMemberAndDevice() { + // given + Member member = createAndSaveMember(); + DeviceToken token = DeviceToken.builder() + .member(member) + .deviceId("device123") + .fcmToken("fcm-token-123") + .build(); + + deviceTokenRepository.save(token); + + // when + Optional foundToken = deviceTokenRepository + .findByMemberIdAndDeviceId(member.getId(), "device123"); + + // then + assertThat(foundToken).isPresent(); + assertThat(foundToken.get().getDeviceId()).isEqualTo("device123"); + assertThat(foundToken.get().getFcmToken()).isEqualTo("fcm-token-123"); + } + + @Test + @DisplayName("존재하지 않는 디바이스의 토큰 조회시 빈 결과가 반환된다.") + void should_ReturnEmpty_When_DeviceNotFound() { + // given + Member member = createAndSaveMember(); + + // when + Optional foundToken = deviceTokenRepository + .findByMemberIdAndDeviceId(member.getId(), "non-existent-device"); + + // then + assertThat(foundToken).isEmpty(); + } + + @Test + @DisplayName("디바이스 토큰이 성공적으로 삭제된다.") + void should_DeleteToken_When_ValidMemberAndDevice() { + // given + Member member = createAndSaveMember(); + DeviceToken token = DeviceToken.builder() + .member(member) + .deviceId("device123") + .fcmToken("fcm-token-123") + .build(); + + deviceTokenRepository.save(token); + + // when + deviceTokenRepository.deleteByMemberIdAndDeviceId(member.getId(), "device123"); + + // then + Optional deletedToken = deviceTokenRepository + .findByMemberIdAndDeviceId(member.getId(), "device123"); + assertThat(deletedToken).isEmpty(); + } + + @Test + @DisplayName("여러 디바이스 토큰이 한번에 삭제된다.") + void should_DeleteAllTokens_When_DeletingMultipleTokens() { + // given + Member member = createAndSaveMember(); + List tokens = List.of( + DeviceToken.builder() + .member(member) + .deviceId("device1") + .fcmToken("token1") + .build(), + DeviceToken.builder() + .member(member) + .deviceId("device2") + .fcmToken("token2") + .build() + ); + + deviceTokenRepository.saveAll(tokens); + + // when + deviceTokenRepository.deleteAll(tokens); + + // then + List remainingTokens = deviceTokenRepository.findAllByMemberId(member.getId()); + assertThat(remainingTokens).isEmpty(); + } + + @Test + @DisplayName("사용자별로 디바이스 토큰이 독립적으로 관리된다.") + void should_ManageTokensSeparately_WhenMultipleUsers() { + // given + Member member1 = createAndSaveMember(); + Member member2 = Member.builder() + .number(5678L) + .nickname("testUser2") + .profileImageUrl("https://example.com/image2.jpg") + .build(); + memberRepository.save(member2); + + DeviceToken token1 = DeviceToken.builder() + .member(member1) + .deviceId("device1") + .fcmToken("token1") + .build(); + DeviceToken token2 = DeviceToken.builder() + .member(member2) + .deviceId("device1") + .fcmToken("token2") + .build(); + + deviceTokenRepository.saveAll(List.of(token1, token2)); + + // when + List tokens1 = deviceTokenRepository.findAllByMemberId(member1.getId()); + List tokens2 = deviceTokenRepository.findAllByMemberId(member2.getId()); + + // then + assertThat(tokens1) + .hasSize(1) + .extracting("fcmToken") + .containsExactly("token1"); + + assertThat(tokens2) + .hasSize(1) + .extracting("fcmToken") + .containsExactly("token2"); + } + +} diff --git a/src/test/java/com/potatocake/everymoment/repository/DiaryRepositoryTest.java b/src/test/java/com/potatocake/everymoment/repository/DiaryRepositoryTest.java index 551d7a5..74f276c 100644 --- a/src/test/java/com/potatocake/everymoment/repository/DiaryRepositoryTest.java +++ b/src/test/java/com/potatocake/everymoment/repository/DiaryRepositoryTest.java @@ -170,7 +170,7 @@ private Member createAndSaveMember() { Member member = Member.builder() .number(1234L) .nickname("testUser") - .profileImageUrl("http://example.com/image.jpg") + .profileImageUrl("https://example.com/image.jpg") .build(); return memberRepository.save(member); } diff --git a/src/test/java/com/potatocake/everymoment/repository/FileRepositoryTest.java b/src/test/java/com/potatocake/everymoment/repository/FileRepositoryTest.java new file mode 100644 index 0000000..29c5422 --- /dev/null +++ b/src/test/java/com/potatocake/everymoment/repository/FileRepositoryTest.java @@ -0,0 +1,206 @@ +package com.potatocake.everymoment.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.potatocake.everymoment.entity.Diary; +import com.potatocake.everymoment.entity.File; +import com.potatocake.everymoment.entity.Member; +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.orm.jpa.DataJpaTest; +import org.springframework.test.context.TestPropertySource; + +@TestPropertySource(properties = "spring.jpa.hibernate.ddl-auto=create-drop") +@DataJpaTest +class FileRepositoryTest { + + @Autowired + private FileRepository fileRepository; + + @Autowired + private DiaryRepository diaryRepository; + + @Autowired + private MemberRepository memberRepository; + + @Test + @DisplayName("파일이 성공적으로 저장된다.") + void should_SaveFile_When_ValidEntity() { + // given + Member member = createAndSaveMember(); + Diary diary = createAndSaveDiary(member); + + File file = File.builder() + .diary(diary) + .imageUrl("https://example.com/image.jpg") + .order(1) + .build(); + + // when + File savedFile = fileRepository.save(file); + + // then + assertThat(savedFile.getId()).isNotNull(); + assertThat(savedFile.getImageUrl()).isEqualTo("https://example.com/image.jpg"); + assertThat(savedFile.getOrder()).isEqualTo(1); + assertThat(savedFile.getDiary()).isEqualTo(diary); + } + + @Test + @DisplayName("일기의 파일 목록이 성공적으로 조회된다.") + void should_FindFiles_When_FilteringByDiaryId() { + // given + Member member = createAndSaveMember(); + Diary diary = createAndSaveDiary(member); + + File file1 = File.builder() + .diary(diary) + .imageUrl("https://example.com/image1.jpg") + .order(1) + .build(); + + File file2 = File.builder() + .diary(diary) + .imageUrl("https://example.com/image2.jpg") + .order(2) + .build(); + + fileRepository.saveAll(List.of(file1, file2)); + + // when + List files = fileRepository.findByDiaryId(diary.getId()); + + // then + assertThat(files).hasSize(2); + assertThat(files).extracting("imageUrl") + .containsExactly( + "https://example.com/image1.jpg", + "https://example.com/image2.jpg" + ); + } + + @Test + @DisplayName("다이어리별로 파일이 독립적으로 조회된다.") + void should_FindFilesSeparately_WhenMultipleDiaries() { + // given + Member member = createAndSaveMember(); + Diary diary1 = createAndSaveDiary(member); + Diary diary2 = createAndSaveDiary(member); + + File file1 = File.builder() + .diary(diary1) + .imageUrl("https://example.com/diary1/image.jpg") + .order(1) + .build(); + + File file2 = File.builder() + .diary(diary2) + .imageUrl("https://example.com/diary2/image.jpg") + .order(1) + .build(); + + fileRepository.saveAll(List.of(file1, file2)); + + // when + List filesForDiary1 = fileRepository.findByDiaryId(diary1.getId()); + List filesForDiary2 = fileRepository.findByDiaryId(diary2.getId()); + + // then + assertThat(filesForDiary1) + .hasSize(1) + .extracting("imageUrl") + .containsExactly("https://example.com/diary1/image.jpg"); + + assertThat(filesForDiary2) + .hasSize(1) + .extracting("imageUrl") + .containsExactly("https://example.com/diary2/image.jpg"); + } + + @Test + @DisplayName("특정 순서의 파일이 성공적으로 조회된다.") + void should_FindFile_When_FilteringByDiaryAndOrder() { + // given + Member member = createAndSaveMember(); + Diary diary = createAndSaveDiary(member); + + File file1 = File.builder() + .diary(diary) + .imageUrl("https://example.com/image1.jpg") + .order(1) + .build(); + + File file2 = File.builder() + .diary(diary) + .imageUrl("https://example.com/image2.jpg") + .order(2) + .build(); + + fileRepository.saveAll(List.of(file1, file2)); + + // when + File foundFile = fileRepository.findByDiaryAndOrder(diary, 2); + + // then + assertThat(foundFile).isNotNull(); + assertThat(foundFile.getImageUrl()).isEqualTo("https://example.com/image2.jpg"); + assertThat(foundFile.getOrder()).isEqualTo(2); + } + + @Test + @DisplayName("일기의 모든 파일이 성공적으로 삭제된다.") + void should_DeleteAllFiles_When_DeletingByDiary() { + // given + Member member = createAndSaveMember(); + Diary diary = createAndSaveDiary(member); + + File file1 = File.builder() + .diary(diary) + .imageUrl("https://example.com/image1.jpg") + .order(1) + .build(); + + File file2 = File.builder() + .diary(diary) + .imageUrl("https://example.com/image2.jpg") + .order(2) + .build(); + + fileRepository.saveAll(List.of(file1, file2)); + + // when + fileRepository.deleteByDiary(diary); + + // then + List remainingFiles = fileRepository.findByDiaryId(diary.getId()); + assertThat(remainingFiles).isEmpty(); + } + + private Member createAndSaveMember() { + Member member = Member.builder() + .number(1234L) + .nickname("testUser") + .profileImageUrl("https://example.com/profile.jpg") + .build(); + return memberRepository.save(member); + } + + private Diary createAndSaveDiary(Member member) { + Point point = new GeometryFactory().createPoint(new Coordinate(37.5665, 126.978)); + + Diary diary = Diary.builder() + .member(member) + .content("Test diary") + .locationName("Test location") + .address("Test address") + .locationPoint(point) + .build(); + return diaryRepository.save(diary); + } + +} diff --git a/src/test/java/com/potatocake/everymoment/repository/FriendRepositoryTest.java b/src/test/java/com/potatocake/everymoment/repository/FriendRepositoryTest.java new file mode 100644 index 0000000..4299dbd --- /dev/null +++ b/src/test/java/com/potatocake/everymoment/repository/FriendRepositoryTest.java @@ -0,0 +1,260 @@ +package com.potatocake.everymoment.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.potatocake.everymoment.entity.Friend; +import com.potatocake.everymoment.entity.Member; +import java.util.List; +import java.util.Optional; +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.orm.jpa.DataJpaTest; +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 FriendRepositoryTest { + + @Autowired + private FriendRepository friendRepository; + + @Autowired + private MemberRepository memberRepository; + + @Test + @DisplayName("친구 관계가 성공적으로 저장된다.") + void should_SaveFriend_When_ValidEntity() { + // given + Member member = createAndSaveMember("user", 123L); + Member friend = createAndSaveMember("friend", 124L); + + Friend friendship = Friend.builder() + .member(member) + .friend(friend) + .build(); + + // when + Friend savedFriendship = friendRepository.save(friendship); + + // then + assertThat(savedFriendship.getId()).isNotNull(); + assertThat(savedFriendship.getMember()).isEqualTo(member); + assertThat(savedFriendship.getFriend()).isEqualTo(friend); + } + + @Test + @DisplayName("회원의 모든 친구가 조회된다.") + void should_FindFriends_When_ValidMember() { + // given + Member member = createAndSaveMember("user", 123L); + Member friend1 = createAndSaveMember("friend1", 124L); + Member friend2 = createAndSaveMember("friend2", 125L); + + friendRepository.save(Friend.builder() + .member(member) + .friend(friend1) + .build()); + friendRepository.save(Friend.builder() + .member(member) + .friend(friend2) + .build()); + + // when + List friends = friendRepository.findFriendsByMember(member); + + // then + assertThat(friends).hasSize(2); + assertThat(friends).extracting(friend -> friend.getFriend().getNickname()) + .containsExactlyInAnyOrder("friend1", "friend2"); + } + + @Test + @DisplayName("특정 친구 관계가 존재하는지 확인된다.") + void should_CheckExistence_When_CheckingFriendship() { + // given + Member member = createAndSaveMember("user", 123L); + Member friend = createAndSaveMember("friend", 124L); + + friendRepository.save(Friend.builder() + .member(member) + .friend(friend) + .build()); + + // when & then + assertThat(friendRepository.existsByMemberIdAndFriendId(member.getId(), friend.getId())) + .isTrue(); + assertThat(friendRepository.existsByMemberIdAndFriendId(member.getId(), 999L)) + .isFalse(); + } + + @Test + @DisplayName("특정 친구 관계가 성공적으로 조회된다.") + void should_FindFriendship_When_ValidMemberAndFriend() { + // given + Member member = createAndSaveMember("user", 123L); + Member friend = createAndSaveMember("friend", 124L); + + Friend friendship = Friend.builder() + .member(member) + .friend(friend) + .build(); + friendRepository.save(friendship); + + // when + Optional foundFriendship = friendRepository.findByMemberAndFriend(member, friend); + + // then + assertThat(foundFriendship).isPresent(); + assertThat(foundFriendship.get().getMember()).isEqualTo(member); + assertThat(foundFriendship.get().getFriend()).isEqualTo(friend); + } + + @Test + @DisplayName("존재하지 않는 친구 관계 조회시 빈 결과가 반환된다.") + void should_ReturnEmpty_When_FriendshipNotFound() { + // given + Member member = createAndSaveMember("user", 123L); + Member notFriend = createAndSaveMember("notFriend", 124L); + + // when + Optional foundFriendship = friendRepository.findByMemberAndFriend(member, notFriend); + + // then + assertThat(foundFriendship).isEmpty(); + } + + @Test + @DisplayName("친구 관계가 성공적으로 삭제된다.") + void should_DeleteFriendship_When_ValidEntity() { + // given + Member member = createAndSaveMember("user", 123L); + Member friend = createAndSaveMember("friend", 124L); + + Friend friendship = Friend.builder() + .member(member) + .friend(friend) + .build(); + Friend savedFriendship = friendRepository.save(friendship); + + // when + friendRepository.delete(savedFriendship); + + // then + Optional deletedFriendship = friendRepository.findByMemberAndFriend(member, friend); + assertThat(deletedFriendship).isEmpty(); + } + + @Test + @DisplayName("친구 관계가 양방향으로 생성된다.") + void should_CreateBidirectionalFriendship_When_AddingFriend() { + // given + Member member1 = createAndSaveMember("user1", 123L); + Member member2 = createAndSaveMember("user2", 124L); + + Friend friendship1 = Friend.builder() + .member(member1) + .friend(member2) + .build(); + Friend friendship2 = Friend.builder() + .member(member2) + .friend(member1) + .build(); + + // when + friendRepository.saveAll(List.of(friendship1, friendship2)); + + // then + assertThat(friendRepository.findByMemberAndFriend(member1, member2)).isPresent(); + assertThat(friendRepository.findByMemberAndFriend(member2, member1)).isPresent(); + } + + @Test + @DisplayName("닉네임으로 친구를 검색할 수 있다.") + void should_FindFriends_When_SearchingByNickname() { + // given + Member member = createAndSaveMember("user", 123L); + Member friend1 = createAndSaveMember("john", 124L); + Member friend2 = createAndSaveMember("johnny", 125L); + Member friend3 = createAndSaveMember("peter", 126L); + + friendRepository.saveAll(List.of( + Friend.builder().member(member).friend(friend1).build(), + Friend.builder().member(member).friend(friend2).build(), + Friend.builder().member(member).friend(friend3).build() + )); + + // when + Specification spec = (root, query, builder) -> { + return builder.and( + builder.equal(root.get("member"), member), + builder.like(root.get("friend").get("nickname"), "%john%") + ); + }; + + Page searchResult = friendRepository.findAll( + spec, + PageRequest.of(0, 10) + ); + + // then + assertThat(searchResult.getContent()) + .hasSize(2) + .extracting(friend -> friend.getFriend().getNickname()) + .containsExactlyInAnyOrder("john", "johnny"); + } + + @Test + @DisplayName("페이징이 성공적으로 동작한다.") + void should_ReturnPagedResult_When_UsingPagination() { + // given + Member member = createAndSaveMember("user", 123L); + List friends = List.of( + createAndSaveMember("friend1", 124L), + createAndSaveMember("friend2", 125L), + createAndSaveMember("friend3", 126L), + createAndSaveMember("friend4", 127L), + createAndSaveMember("friend5", 128L) + ); + + List friendships = friends.stream() + .map(friend -> Friend.builder() + .member(member) + .friend(friend) + .build()) + .toList(); + + friendRepository.saveAll(friendships); + + // when + Page firstPage = friendRepository.findAll( + (root, query, builder) -> builder.equal(root.get("member"), member), + PageRequest.of(0, 2) + ); + + Page secondPage = friendRepository.findAll( + (root, query, builder) -> builder.equal(root.get("member"), member), + PageRequest.of(1, 2) + ); + + // then + assertThat(firstPage.getContent()).hasSize(2); + assertThat(firstPage.hasNext()).isTrue(); + + assertThat(secondPage.getContent()).hasSize(2); + assertThat(secondPage.hasNext()).isTrue(); + } + + private Member createAndSaveMember(String nickname, Long number) { + Member member = Member.builder() + .number(number) + .nickname(nickname) + .profileImageUrl("https://example.com/profile.jpg") + .build(); + return memberRepository.save(member); + } + +} diff --git a/src/test/java/com/potatocake/everymoment/repository/FriendRequestRepositoryTest.java b/src/test/java/com/potatocake/everymoment/repository/FriendRequestRepositoryTest.java new file mode 100644 index 0000000..7e09352 --- /dev/null +++ b/src/test/java/com/potatocake/everymoment/repository/FriendRequestRepositoryTest.java @@ -0,0 +1,155 @@ +package com.potatocake.everymoment.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.potatocake.everymoment.entity.FriendRequest; +import com.potatocake.everymoment.entity.Member; +import java.util.List; +import java.util.Optional; +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.orm.jpa.DataJpaTest; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.ScrollPosition; +import org.springframework.data.domain.Window; +import org.springframework.test.context.TestPropertySource; + +@TestPropertySource(properties = "spring.jpa.hibernate.ddl-auto=create-drop") +@DataJpaTest +class FriendRequestRepositoryTest { + + @Autowired + private FriendRequestRepository friendRequestRepository; + + @Autowired + private MemberRepository memberRepository; + + @Test + @DisplayName("친구 요청이 성공적으로 저장된다.") + void should_SaveFriendRequest_When_ValidEntity() { + // given + Member sender = createAndSaveMember("sender", 123L); + Member receiver = createAndSaveMember("receiver", 124L); + + FriendRequest request = FriendRequest.builder() + .sender(sender) + .receiver(receiver) + .build(); + + // when + FriendRequest savedRequest = friendRequestRepository.save(request); + + // then + assertThat(savedRequest.getId()).isNotNull(); + assertThat(savedRequest.getSender()).isEqualTo(sender); + assertThat(savedRequest.getReceiver()).isEqualTo(receiver); + } + + @Test + @DisplayName("사용자의 받은 친구 요청 목록이 성공적으로 조회된다.") + void should_FindReceivedRequests_When_ValidReceiver() { + // given + Member receiver = createAndSaveMember("receiver", 123L); + Member sender1 = createAndSaveMember("sender1", 124L); + Member sender2 = createAndSaveMember("sender2", 125L); + + friendRequestRepository.saveAll(List.of( + FriendRequest.builder() + .sender(sender1) + .receiver(receiver) + .build(), + FriendRequest.builder() + .sender(sender2) + .receiver(receiver) + .build() + )); + + // when + Window requests = friendRequestRepository.findByReceiverId( + receiver.getId(), + ScrollPosition.offset(), + PageRequest.of(0, 10) + ); + + // then + assertThat(requests.getContent()).hasSize(2); + assertThat(requests.getContent()) + .extracting(request -> request.getSender().getNickname()) + .containsExactlyInAnyOrder("sender1", "sender2"); + } + + @Test + @DisplayName("이미 존재하는 친구 요청인지 확인된다.") + void should_CheckExistence_When_CheckingRequest() { + // given + Member sender = createAndSaveMember("sender", 123L); + Member receiver = createAndSaveMember("receiver", 124L); + + FriendRequest request = FriendRequest.builder() + .sender(sender) + .receiver(receiver) + .build(); + friendRequestRepository.save(request); + + // when & then + assertThat(friendRequestRepository.existsBySenderIdAndReceiverId(sender.getId(), receiver.getId())) + .isTrue(); + assertThat(friendRequestRepository.existsBySenderIdAndReceiverId(receiver.getId(), sender.getId())) + .isFalse(); + } + + @Test + @DisplayName("특정 친구 요청이 성공적으로 조회된다.") + void should_FindRequest_When_ValidSenderAndReceiver() { + // given + Member sender = createAndSaveMember("sender", 123L); + Member receiver = createAndSaveMember("receiver", 124L); + + FriendRequest request = FriendRequest.builder() + .sender(sender) + .receiver(receiver) + .build(); + friendRequestRepository.save(request); + + // when + Optional foundRequest = friendRequestRepository + .findBySenderIdAndReceiverId(sender.getId(), receiver.getId()); + + // then + assertThat(foundRequest).isPresent(); + assertThat(foundRequest.get().getSender()).isEqualTo(sender); + assertThat(foundRequest.get().getReceiver()).isEqualTo(receiver); + } + + @Test + @DisplayName("친구 요청이 성공적으로 삭제된다.") + void should_DeleteRequest_When_ValidEntity() { + // given + Member sender = createAndSaveMember("sender", 123L); + Member receiver = createAndSaveMember("receiver", 124L); + + FriendRequest request = FriendRequest.builder() + .sender(sender) + .receiver(receiver) + .build(); + FriendRequest savedRequest = friendRequestRepository.save(request); + + // when + friendRequestRepository.delete(savedRequest); + + // then + Optional deletedRequest = friendRequestRepository.findById(savedRequest.getId()); + assertThat(deletedRequest).isEmpty(); + } + + private Member createAndSaveMember(String nickname, Long number) { + Member member = Member.builder() + .number(number) + .nickname(nickname) + .profileImageUrl("https://example.com/profile.jpg") + .build(); + return memberRepository.save(member); + } + +} diff --git a/src/test/java/com/potatocake/everymoment/repository/LikeRepositoryTest.java b/src/test/java/com/potatocake/everymoment/repository/LikeRepositoryTest.java new file mode 100644 index 0000000..87027c4 --- /dev/null +++ b/src/test/java/com/potatocake/everymoment/repository/LikeRepositoryTest.java @@ -0,0 +1,170 @@ +package com.potatocake.everymoment.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.potatocake.everymoment.entity.Diary; +import com.potatocake.everymoment.entity.Like; +import com.potatocake.everymoment.entity.Member; +import java.util.List; +import java.util.Optional; +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.orm.jpa.DataJpaTest; +import org.springframework.test.context.TestPropertySource; + +@TestPropertySource(properties = "spring.jpa.hibernate.ddl-auto=create-drop") +@DataJpaTest +class LikeRepositoryTest { + + @Autowired + private LikeRepository likeRepository; + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private DiaryRepository diaryRepository; + + @Test + @DisplayName("좋아요가 성공적으로 저장된다.") + void should_SaveLike_When_ValidEntity() { + // given + Member member = createAndSaveMember(); + Diary diary = createAndSaveDiary(member); + + Like like = Like.builder() + .member(member) + .diary(diary) + .build(); + + // when + Like savedLike = likeRepository.save(like); + + // then + assertThat(savedLike.getId()).isNotNull(); + assertThat(savedLike.getMember()).isEqualTo(member); + assertThat(savedLike.getDiary()).isEqualTo(diary); + } + + @Test + @DisplayName("일기의 좋아요 수가 성공적으로 조회된다.") + void should_CountLikes_When_CountingByDiary() { + // given + Member member1 = createAndSaveMember(); + Member member2 = Member.builder() + .number(5678L) + .nickname("testUser2") + .profileImageUrl("https://example.com/profile2.jpg") + .build(); + memberRepository.save(member2); + + Diary diary = createAndSaveDiary(member1); + + Like like1 = Like.builder() + .member(member1) + .diary(diary) + .build(); + Like like2 = Like.builder() + .member(member2) + .diary(diary) + .build(); + + likeRepository.saveAll(List.of(like1, like2)); + + // when + Long likeCount = likeRepository.countByDiary(diary); + + // then + assertThat(likeCount).isEqualTo(2L); + } + + @Test + @DisplayName("특정 사용자의 좋아요가 성공적으로 조회된다.") + void should_FindLike_When_FilteringByMemberAndDiary() { + // given + Member member = createAndSaveMember(); + Diary diary = createAndSaveDiary(member); + + Like like = Like.builder() + .member(member) + .diary(diary) + .build(); + likeRepository.save(like); + + // when + Optional foundLike = likeRepository.findByMemberIdAndDiaryId(member.getId(), diary.getId()); + + // then + assertThat(foundLike).isPresent(); + assertThat(foundLike.get().getMember()).isEqualTo(member); + assertThat(foundLike.get().getDiary()).isEqualTo(diary); + } + + @Test + @DisplayName("좋아요 여부가 성공적으로 확인된다.") + void should_CheckExistence_When_CheckingLike() { + // given + Member member = createAndSaveMember(); + Diary diary = createAndSaveDiary(member); + + Like like = Like.builder() + .member(member) + .diary(diary) + .build(); + likeRepository.save(like); + + // when & then + assertThat(likeRepository.existsByMemberIdAndDiaryId(member.getId(), diary.getId())) + .isTrue(); + assertThat(likeRepository.existsByMemberIdAndDiaryId(member.getId(), 999L)) + .isFalse(); + } + + @Test + @DisplayName("좋아요가 성공적으로 삭제된다.") + void should_DeleteLike_When_ValidEntity() { + // given + Member member = createAndSaveMember(); + Diary diary = createAndSaveDiary(member); + + Like like = Like.builder() + .member(member) + .diary(diary) + .build(); + Like savedLike = likeRepository.save(like); + + // when + likeRepository.delete(savedLike); + + // then + Optional deletedLike = likeRepository.findById(savedLike.getId()); + assertThat(deletedLike).isEmpty(); + } + + private Member createAndSaveMember() { + Member member = Member.builder() + .number(1234L) + .nickname("testUser") + .profileImageUrl("https://example.com/profile.jpg") + .build(); + return memberRepository.save(member); + } + + private Diary createAndSaveDiary(Member member) { + Point point = new GeometryFactory().createPoint(new Coordinate(37.5665, 126.978)); + + Diary diary = Diary.builder() + .member(member) + .content("Test content") + .locationName("Test location") + .address("Test address") + .locationPoint(point) + .build(); + return diaryRepository.save(diary); + } + +} diff --git a/src/test/java/com/potatocake/everymoment/repository/MemberRepositoryTest.java b/src/test/java/com/potatocake/everymoment/repository/MemberRepositoryTest.java new file mode 100644 index 0000000..f28ec1a --- /dev/null +++ b/src/test/java/com/potatocake/everymoment/repository/MemberRepositoryTest.java @@ -0,0 +1,177 @@ +package com.potatocake.everymoment.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.potatocake.everymoment.entity.Member; +import java.util.List; +import java.util.Optional; +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.orm.jpa.DataJpaTest; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.ScrollPosition; +import org.springframework.data.domain.Window; +import org.springframework.test.context.TestPropertySource; + +@TestPropertySource(properties = "spring.jpa.hibernate.ddl-auto=create-drop") +@DataJpaTest +class MemberRepositoryTest { + + @Autowired + private MemberRepository memberRepository; + + @Test + @DisplayName("회원이 성공적으로 저장된다.") + void should_SaveMember_When_ValidEntity() { + // given + Member member = Member.builder() + .number(1234L) + .nickname("testUser") + .profileImageUrl("https://example.com/profile.jpg") + .build(); + + // when + Member savedMember = memberRepository.save(member); + + // then + assertThat(savedMember.getId()).isNotNull(); + assertThat(savedMember.getNumber()).isEqualTo(1234L); + assertThat(savedMember.getNickname()).isEqualTo("testUser"); + assertThat(savedMember.getProfileImageUrl()).isEqualTo("https://example.com/profile.jpg"); + } + + @Test + @DisplayName("회원 번호로 회원이 성공적으로 조회된다.") + void should_FindMember_When_SearchingByNumber() { + // given + Member member = Member.builder() + .number(1234L) + .nickname("testUser") + .profileImageUrl("https://example.com/profile.jpg") + .build(); + memberRepository.save(member); + + // when + Optional foundMember = memberRepository.findByNumber(1234L); + + // then + assertThat(foundMember).isPresent(); + assertThat(foundMember.get().getNickname()).isEqualTo("testUser"); + } + + @Test + @DisplayName("닉네임으로 회원이 성공적으로 검색된다.") + void should_FindMembers_When_SearchingByNickname() { + // given + Member member1 = Member.builder() + .number(1234L) + .nickname("john") + .profileImageUrl("https://example.com/profile1.jpg") + .build(); + Member member2 = Member.builder() + .number(5678L) + .nickname("johnny") + .profileImageUrl("https://example.com/profile2.jpg") + .build(); + Member member3 = Member.builder() + .number(9012L) + .nickname("peter") + .profileImageUrl("https://example.com/profile3.jpg") + .build(); + + memberRepository.saveAll(List.of(member1, member2, member3)); + + // when + Window result = memberRepository.findByNicknameContaining( + "john", + ScrollPosition.offset(), + PageRequest.of(0, 10) + ); + + // then + assertThat(result.getContent()).hasSize(2) + .extracting("nickname") + .containsExactlyInAnyOrder("john", "johnny"); + } + + @Test + @DisplayName("회원 번호 존재 여부가 성공적으로 확인된다.") + void should_CheckExistence_When_CheckingNumber() { + // given + Member member = Member.builder() + .number(1234L) + .nickname("testUser") + .profileImageUrl("https://example.com/profile.jpg") + .build(); + memberRepository.save(member); + + // when & then + assertThat(memberRepository.existsByNumber(1234L)).isTrue(); + assertThat(memberRepository.existsByNumber(5678L)).isFalse(); + } + + @Test + @DisplayName("다음 익명 회원 번호가 성공적으로 생성된다.") + void should_GenerateNextNumber_When_CreatingAnonymous() { + // given + Member member1 = Member.builder() + .number(-1L) + .nickname("anonymous1") + .profileImageUrl("https://example.com/profile1.jpg") + .build(); + Member member2 = Member.builder() + .number(-2L) + .nickname("anonymous2") + .profileImageUrl("https://example.com/profile2.jpg") + .build(); + + memberRepository.saveAll(List.of(member1, member2)); + + // when + Long nextNumber = memberRepository.findNextAnonymousNumber(); + + // then + assertThat(nextNumber).isEqualTo(-3L); + } + + @Test + @DisplayName("회원이 성공적으로 삭제된다.") + void should_DeleteMember_When_ValidEntity() { + // given + Member member = Member.builder() + .number(1234L) + .nickname("testUser") + .profileImageUrl("https://example.com/profile.jpg") + .build(); + Member savedMember = memberRepository.save(member); + + // when + memberRepository.delete(savedMember); + + // then + Optional deletedMember = memberRepository.findById(savedMember.getId()); + assertThat(deletedMember).isEmpty(); + } + + @Test + @DisplayName("회원 정보가 성공적으로 업데이트된다.") + void should_UpdateMember_When_ValidInput() { + // given + Member member = Member.builder() + .number(1234L) + .nickname("oldNickname") + .profileImageUrl("https://example.com/old.jpg") + .build(); + memberRepository.save(member); + + // when + member.update("newNickname", "https://example.com/new.jpg"); + + // then + Member updatedMember = memberRepository.findByNumber(1234L).orElseThrow(); + assertThat(updatedMember.getNickname()).isEqualTo("newNickname"); + assertThat(updatedMember.getProfileImageUrl()).isEqualTo("https://example.com/new.jpg"); + } + +} diff --git a/src/test/java/com/potatocake/everymoment/service/CategoryServiceTest.java b/src/test/java/com/potatocake/everymoment/service/CategoryServiceTest.java new file mode 100644 index 0000000..c2b931d --- /dev/null +++ b/src/test/java/com/potatocake/everymoment/service/CategoryServiceTest.java @@ -0,0 +1,168 @@ +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.BDDMockito.given; +import static org.mockito.BDDMockito.then; + +import com.potatocake.everymoment.dto.request.CategoryCreateRequest; +import com.potatocake.everymoment.dto.response.CategoryResponse; +import com.potatocake.everymoment.entity.Category; +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.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.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class CategoryServiceTest { + + @InjectMocks + private CategoryService categoryService; + + @Mock + private CategoryRepository categoryRepository; + + @Mock + private MemberRepository memberRepository; + + @Test + @DisplayName("카테고리가 성공적으로 추가된다.") + void should_AddCategory_When_ValidInput() { + // given + Long memberId = 1L; + Member member = Member.builder() + .id(memberId) + .build(); + + CategoryCreateRequest request = new CategoryCreateRequest("New Category"); + + Category category = Category.builder() + .id(1L) + .member(member) + .categoryName("New Category") + .build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(categoryRepository.save(any(Category.class))).willReturn(category); + + // when + categoryService.addCategory(memberId, request); + + // then + then(memberRepository).should().findById(memberId); + then(categoryRepository).should().save(any(Category.class)); + } + + @Test + @DisplayName("카테고리 목록이 성공적으로 조회된다.") + void should_GetCategories_When_ValidMemberId() { + // given + Long memberId = 1L; + List categories = List.of( + Category.builder().id(1L).categoryName("Category 1").build(), + Category.builder().id(2L).categoryName("Category 2").build() + ); + + given(categoryRepository.findByMemberId(memberId)).willReturn(categories); + + // when + List responses = categoryService.getCategories(memberId); + + // then + assertThat(responses).hasSize(2); + assertThat(responses).extracting("categoryName") + .containsExactly("Category 1", "Category 2"); + then(categoryRepository).should().findByMemberId(memberId); + } + + @Test + @DisplayName("카테고리가 성공적으로 수정된다.") + void should_UpdateCategory_When_ValidInput() { + // given + Long categoryId = 1L; + Long memberId = 1L; + Member member = Member.builder() + .id(memberId) + .build(); + + Category category = Category.builder() + .id(categoryId) + .member(member) + .categoryName("Original Name") + .build(); + + CategoryCreateRequest request = new CategoryCreateRequest("Updated Name"); + + given(categoryRepository.findById(categoryId)).willReturn(Optional.of(category)); + + // when + categoryService.updateCategory(categoryId, memberId, request); + + // then + assertThat(category.getCategoryName()).isEqualTo("Updated Name"); + then(categoryRepository).should().findById(categoryId); + } + + @Test + @DisplayName("다른 사용자의 카테고리를 수정하려고 하면 예외가 발생한다.") + void should_ThrowException_When_UpdateOtherUserCategory() { + // given + Long categoryId = 1L; + Long memberId = 1L; + Member owner = Member.builder() + .id(2L) + .build(); + + Category category = Category.builder() + .id(categoryId) + .member(owner) + .categoryName("Original Name") + .build(); + + CategoryCreateRequest request = new CategoryCreateRequest("Updated Name"); + + given(categoryRepository.findById(categoryId)).willReturn(Optional.of(category)); + + // when & then + assertThatThrownBy(() -> categoryService.updateCategory(categoryId, memberId, request)) + .isInstanceOf(GlobalException.class) + .hasFieldOrPropertyWithValue("errorCode", ErrorCode.CATEGORY_NOT_OWNER); + } + + @Test + @DisplayName("카테고리가 성공적으로 삭제된다.") + void should_DeleteCategory_When_ValidRequest() { + // given + Long categoryId = 1L; + Long memberId = 1L; + Member member = Member.builder() + .id(memberId) + .build(); + + Category category = Category.builder() + .id(categoryId) + .member(member) + .categoryName("Category") + .build(); + + given(categoryRepository.findById(categoryId)).willReturn(Optional.of(category)); + + // when + categoryService.deleteCategory(categoryId, memberId); + + // then + then(categoryRepository).should().findById(categoryId); + then(categoryRepository).should().delete(category); + } + +} diff --git a/src/test/java/com/potatocake/everymoment/service/CommentServiceTest.java b/src/test/java/com/potatocake/everymoment/service/CommentServiceTest.java new file mode 100644 index 0000000..da7f913 --- /dev/null +++ b/src/test/java/com/potatocake/everymoment/service/CommentServiceTest.java @@ -0,0 +1,219 @@ +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 com.potatocake.everymoment.constant.NotificationType; +import com.potatocake.everymoment.dto.request.CommentRequest; +import com.potatocake.everymoment.dto.response.CommentsResponse; +import com.potatocake.everymoment.entity.Comment; +import com.potatocake.everymoment.entity.Diary; +import com.potatocake.everymoment.entity.Member; +import com.potatocake.everymoment.exception.ErrorCode; +import com.potatocake.everymoment.exception.GlobalException; +import com.potatocake.everymoment.repository.CommentRepository; +import com.potatocake.everymoment.repository.DiaryRepository; +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.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.PageRequest; + +@ExtendWith(MockitoExtension.class) +class CommentServiceTest { + + @InjectMocks + private CommentService commentService; + + @Mock + private CommentRepository commentRepository; + + @Mock + private DiaryRepository diaryRepository; + + @Mock + private MemberRepository memberRepository; + + @Mock + private NotificationService notificationService; + + @Test + @DisplayName("댓글 목록이 성공적으로 조회된다.") + void should_GetComments_When_ValidDiaryId() { + // given + Long diaryId = 1L; + Member member = Member.builder() + .id(1L) + .nickname("testUser") + .build(); + Comment comment = Comment.builder() + .id(1L) + .member(member) + .content("Test comment") + .build(); + + Page commentPage = new PageImpl<>(List.of(comment)); + + given(commentRepository.findAllByDiaryId(eq(diaryId), any(PageRequest.class))) + .willReturn(commentPage); + + // when + CommentsResponse response = commentService.getComments(diaryId, 0, 10); + + // then + assertThat(response.getComments()).hasSize(1); + assertThat(response.getComments().get(0).getContent()).isEqualTo("Test comment"); + then(commentRepository).should().findAllByDiaryId(eq(diaryId), any(PageRequest.class)); + } + + @Test + @DisplayName("댓글이 성공적으로 생성된다.") + void should_CreateComment_When_ValidInput() { + // given + Long memberId = 1L; + Long diaryId = 1L; + Member member = Member.builder() + .id(memberId) + .nickname("testUser") + .build(); + + Member diaryOwner = Member.builder() + .id(2L) + .build(); + + Diary diary = Diary.builder() + .id(diaryId) + .member(diaryOwner) + .isPublic(true) + .build(); + + CommentRequest request = new CommentRequest(); + request.setContent("New comment"); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(diaryRepository.findById(diaryId)).willReturn(Optional.of(diary)); + given(commentRepository.save(any(Comment.class))).willReturn( + Comment.builder() + .id(1L) + .member(member) + .diary(diary) + .content(request.getContent()) + .build() + ); + + // when + commentService.createComment(memberId, diaryId, request); + + // then + then(commentRepository).should().save(any(Comment.class)); + then(notificationService).should().createAndSendNotification( + eq(diary.getMember().getId()), + eq(NotificationType.COMMENT), + eq(diaryId), + eq(member.getNickname()) + ); + } + + @Test + @DisplayName("자신의 일기에 댓글을 작성할 때는 알림이 발송되지 않는다.") + void should_NotSendNotification_When_CommentingOwnDiary() { + // given + Long memberId = 1L; + Long diaryId = 1L; + Member member = Member.builder() + .id(memberId) + .nickname("testUser") + .build(); + + Diary diary = Diary.builder() + .id(diaryId) + .member(member) // 일기 작성자와 댓글 작성자가 동일 + .isPublic(true) + .build(); + + CommentRequest request = new CommentRequest(); + request.setContent("New comment"); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(diaryRepository.findById(diaryId)).willReturn(Optional.of(diary)); + given(commentRepository.save(any(Comment.class))).willReturn( + Comment.builder() + .id(1L) + .member(member) + .diary(diary) + .content(request.getContent()) + .build() + ); + + // when + commentService.createComment(memberId, diaryId, request); + + // then + then(commentRepository).should().save(any(Comment.class)); + then(notificationService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("비공개 일기에 댓글을 작성하려고 하면 예외가 발생한다.") + void should_ThrowException_When_DiaryNotPublic() { + // given + Long memberId = 1L; + Long diaryId = 1L; + Member member = Member.builder() + .id(memberId) + .build(); + Diary diary = Diary.builder() + .id(diaryId) + .isPublic(false) + .build(); + CommentRequest request = new CommentRequest(); + request.setContent("New comment"); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(diaryRepository.findById(diaryId)).willReturn(Optional.of(diary)); + + // when & then + assertThatThrownBy(() -> commentService.createComment(memberId, diaryId, request)) + .isInstanceOf(GlobalException.class) + .hasFieldOrPropertyWithValue("errorCode", ErrorCode.DIARY_NOT_PUBLIC); + } + + @Test + @DisplayName("댓글이 성공적으로 수정된다.") + void should_UpdateComment_When_ValidInput() { + // given + Long memberId = 1L; + Long commentId = 1L; + Member member = Member.builder() + .id(memberId) + .build(); + Comment comment = Comment.builder() + .id(commentId) + .member(member) + .content("Original content") + .build(); + CommentRequest request = new CommentRequest(); + request.setContent("Updated content"); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(commentRepository.findById(commentId)).willReturn(Optional.of(comment)); + + // when + commentService.updateComment(memberId, commentId, request); + + // then + assertThat(comment.getContent()).isEqualTo("Updated content"); + } + +} diff --git a/src/test/java/com/potatocake/everymoment/service/FcmServiceTest.java b/src/test/java/com/potatocake/everymoment/service/FcmServiceTest.java new file mode 100644 index 0000000..3713901 --- /dev/null +++ b/src/test/java/com/potatocake/everymoment/service/FcmServiceTest.java @@ -0,0 +1,181 @@ +package com.potatocake.everymoment.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; +import static org.mockito.BDDMockito.willDoNothing; +import static org.mockito.Mockito.mock; + +import com.google.firebase.messaging.BatchResponse; +import com.google.firebase.messaging.FirebaseMessaging; +import com.google.firebase.messaging.FirebaseMessagingException; +import com.google.firebase.messaging.MessagingErrorCode; +import com.google.firebase.messaging.SendResponse; +import com.potatocake.everymoment.dto.request.FcmNotificationRequest; +import com.potatocake.everymoment.entity.DeviceToken; +import com.potatocake.everymoment.entity.Member; +import com.potatocake.everymoment.repository.DeviceTokenRepository; +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.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class FcmServiceTest { + + @InjectMocks + private FcmService fcmService; + + @Mock + private FirebaseMessaging firebaseMessaging; + + @Mock + private DeviceTokenRepository deviceTokenRepository; + + @Mock + private MemberRepository memberRepository; + + @Test + @DisplayName("알림이 성공적으로 전송된다.") + void should_SendNotification_When_ValidInput() throws Exception { + // given + Long targetMemberId = 1L; + DeviceToken deviceToken = DeviceToken.builder() + .fcmToken("fcm-token-123") + .build(); + + FcmNotificationRequest request = FcmNotificationRequest.builder() + .title("Test Title") + .body("Test Body") + .type("TEST") + .targetId(1L) + .build(); + + BatchResponse batchResponse = mock(BatchResponse.class); + SendResponse sendResponse = mock(SendResponse.class); + given(sendResponse.isSuccessful()).willReturn(true); + given(batchResponse.getResponses()).willReturn(List.of(sendResponse)); + + given(deviceTokenRepository.findAllByMemberId(targetMemberId)) + .willReturn(List.of(deviceToken)); + given(firebaseMessaging.sendEach(any())).willReturn(batchResponse); + + // when + fcmService.sendNotification(targetMemberId, request); + + // then + then(firebaseMessaging).should().sendEach(any()); + } + + @Test + @DisplayName("토큰이 성공적으로 등록된다.") + void should_RegisterToken_When_ValidInput() { + // given + Long memberId = 1L; + String deviceId = "device123"; + String fcmToken = "fcm-token-123"; + + Member member = Member.builder() + .id(memberId) + .build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(deviceTokenRepository.findByMemberIdAndDeviceId(memberId, deviceId)) + .willReturn(Optional.empty()); + + // when + fcmService.registerToken(memberId, deviceId, fcmToken); + + // then + then(deviceTokenRepository).should().save(any(DeviceToken.class)); + } + + @Test + @DisplayName("기존 토큰이 성공적으로 업데이트된다.") + void should_UpdateToken_When_TokenExists() { + // given + Long memberId = 1L; + String deviceId = "device123"; + String fcmToken = "fcm-token-123"; + + Member member = Member.builder() + .id(memberId) + .build(); + + DeviceToken existingToken = DeviceToken.builder() + .member(member) + .deviceId(deviceId) + .fcmToken("old-token") + .build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(deviceTokenRepository.findByMemberIdAndDeviceId(memberId, deviceId)) + .willReturn(Optional.of(existingToken)); + + // when + fcmService.registerToken(memberId, deviceId, fcmToken); + + // then + assertThat(existingToken.getFcmToken()).isEqualTo(fcmToken); + } + + @Test + @DisplayName("토큰이 성공적으로 삭제된다.") + void should_RemoveToken_When_ValidInput() { + // given + Long memberId = 1L; + String deviceId = "device123"; + + willDoNothing().given(deviceTokenRepository) + .deleteByMemberIdAndDeviceId(memberId, deviceId); + + // when + fcmService.removeToken(memberId, deviceId); + + // then + then(deviceTokenRepository).should().deleteByMemberIdAndDeviceId(memberId, deviceId); + } + + @Test + @DisplayName("잘못된 토큰은 자동으로 삭제된다.") + void should_DeleteToken_When_TokenInvalid() throws Exception { + // given + Long targetMemberId = 1L; + DeviceToken deviceToken = DeviceToken.builder() + .fcmToken("invalid-token") + .build(); + + FcmNotificationRequest request = FcmNotificationRequest.builder() + .title("Test Title") + .body("Test Body") + .type("TEST") + .targetId(1L) + .build(); + + BatchResponse batchResponse = mock(BatchResponse.class); + SendResponse sendResponse = mock(SendResponse.class); + FirebaseMessagingException exception = mock(FirebaseMessagingException.class); + + given(sendResponse.isSuccessful()).willReturn(false); + given(sendResponse.getException()).willReturn(exception); + given(exception.getMessagingErrorCode()).willReturn(MessagingErrorCode.UNREGISTERED); + given(batchResponse.getResponses()).willReturn(List.of(sendResponse)); + + given(deviceTokenRepository.findAllByMemberId(targetMemberId)) + .willReturn(List.of(deviceToken)); + given(firebaseMessaging.sendEach(any())).willReturn(batchResponse); + + // when + fcmService.sendNotification(targetMemberId, request); + + // then + then(deviceTokenRepository).should().deleteAll(any()); + } + +} diff --git a/src/test/java/com/potatocake/everymoment/service/FileServiceTest.java b/src/test/java/com/potatocake/everymoment/service/FileServiceTest.java new file mode 100644 index 0000000..d2f07a7 --- /dev/null +++ b/src/test/java/com/potatocake/everymoment/service/FileServiceTest.java @@ -0,0 +1,134 @@ +package com.potatocake.everymoment.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.mock; + +import com.potatocake.everymoment.dto.response.FileResponse; +import com.potatocake.everymoment.entity.Diary; +import com.potatocake.everymoment.entity.File; +import com.potatocake.everymoment.entity.Member; +import com.potatocake.everymoment.repository.DiaryRepository; +import com.potatocake.everymoment.repository.FileRepository; +import com.potatocake.everymoment.util.S3FileUploader; +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.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.web.multipart.MultipartFile; + +@ExtendWith(MockitoExtension.class) +class FileServiceTest { + + @InjectMocks + private FileService fileService; + + @Mock + private FileRepository fileRepository; + + @Mock + private DiaryRepository diaryRepository; + + @Mock + private S3FileUploader uploader; + + @Test + @DisplayName("파일 목록이 성공적으로 조회된다.") + void should_GetFiles_When_ValidDiaryId() { + // given + Long diaryId = 1L; + List files = List.of( + File.builder() + .id(1L) + .imageUrl("https://example.com/image1.jpg") + .order(1) + .build(), + File.builder() + .id(2L) + .imageUrl("https://example.com/image2.jpg") + .order(2) + .build() + ); + + given(fileRepository.findByDiaryId(diaryId)).willReturn(files); + + // when + List responses = fileService.getFiles(diaryId); + + // then + assertThat(responses).hasSize(2); + assertThat(responses).extracting("imageUrl") + .containsExactly( + "https://example.com/image1.jpg", + "https://example.com/image2.jpg" + ); + } + + @Test + @DisplayName("파일이 성공적으로 업로드된다.") + void should_UploadFiles_When_ValidInput() { + // given + Long diaryId = 1L; + Long memberId = 1L; + Member member = Member.builder() + .id(memberId) + .build(); + Diary diary = Diary.builder() + .id(diaryId) + .member(member) + .build(); + + MultipartFile file1 = mock(MultipartFile.class); + MultipartFile file2 = mock(MultipartFile.class); + List files = List.of(file1, file2); + + given(diaryRepository.findById(diaryId)).willReturn(Optional.of(diary)); + given(uploader.uploadFile(any(MultipartFile.class))) + .willReturn("https://example.com/image1.jpg") + .willReturn("https://example.com/image2.jpg"); + + // when + fileService.uploadFiles(diaryId, memberId, files); + + // then + then(fileRepository).should().saveAll(anyList()); + then(uploader).should().uploadFile(file1); + then(uploader).should().uploadFile(file2); + } + + @Test + @DisplayName("파일이 성공적으로 수정된다.") + void should_UpdateFiles_When_ValidInput() { + // given + Long diaryId = 1L; + Long memberId = 1L; + Member member = Member.builder() + .id(memberId) + .build(); + Diary diary = Diary.builder() + .id(diaryId) + .member(member) + .build(); + + MultipartFile file = mock(MultipartFile.class); + List files = List.of(file); + + given(diaryRepository.findById(diaryId)).willReturn(Optional.of(diary)); + given(uploader.uploadFile(file)).willReturn("https://example.com/new-image.jpg"); + + // when + fileService.updateFiles(diaryId, memberId, files); + + // then + then(fileRepository).should().deleteByDiary(diary); + then(fileRepository).should().saveAll(anyList()); + } + +} diff --git a/src/test/java/com/potatocake/everymoment/service/FriendRequestServiceTest.java b/src/test/java/com/potatocake/everymoment/service/FriendRequestServiceTest.java new file mode 100644 index 0000000..35c6548 --- /dev/null +++ b/src/test/java/com/potatocake/everymoment/service/FriendRequestServiceTest.java @@ -0,0 +1,297 @@ +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.internal.verification.VerificationModeFactory.times; + +import com.potatocake.everymoment.constant.NotificationType; +import com.potatocake.everymoment.dto.response.FriendRequestPageRequest; +import com.potatocake.everymoment.entity.Friend; +import com.potatocake.everymoment.entity.FriendRequest; +import com.potatocake.everymoment.entity.Member; +import com.potatocake.everymoment.exception.ErrorCode; +import com.potatocake.everymoment.exception.GlobalException; +import com.potatocake.everymoment.repository.FriendRepository; +import com.potatocake.everymoment.repository.FriendRequestRepository; +import com.potatocake.everymoment.repository.MemberRepository; +import com.potatocake.everymoment.util.PagingUtil; +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.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.ScrollPosition; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Window; + +@ExtendWith(MockitoExtension.class) +class FriendRequestServiceTest { + + @InjectMocks + private FriendRequestService friendRequestService; + + @Mock + private FriendRequestRepository friendRequestRepository; + + @Mock + private FriendRepository friendRepository; + + @Mock + private MemberRepository memberRepository; + + @Mock + private NotificationService notificationService; + + @Mock + private PagingUtil pagingUtil; + + @Test + @DisplayName("친구 요청 목록이 성공적으로 조회된다.") + void should_GetFriendRequests_When_ValidRequest() { + // given + Long memberId = 1L; + Long key = null; + int size = 10; + + Member requester = Member.builder() + .id(2L) + .nickname("requester") + .profileImageUrl("https://example.com/profile.jpg") + .build(); + + FriendRequest request = FriendRequest.builder() + .id(1L) + .sender(requester) + .build(); + + ScrollPosition scrollPosition = ScrollPosition.offset(); + Pageable pageable = PageRequest.of(0, size); + Window window = Window.from(List.of(request), i -> scrollPosition, false); + + given(pagingUtil.createScrollPosition(key)).willReturn(scrollPosition); + given(pagingUtil.createPageable(size, Sort.Direction.DESC)).willReturn(pageable); + given(friendRequestRepository.findByReceiverId(memberId, scrollPosition, pageable)) + .willReturn(window); + given(memberRepository.findAllById(any())).willReturn(List.of(requester)); + + given(pagingUtil.getNextKey(any(), any())).willReturn(null); + + // when + FriendRequestPageRequest response = friendRequestService.getFriendRequests(key, size, memberId); + + // then + assertThat(response.getFriendRequests()).hasSize(1); + assertThat(response.getFriendRequests().get(0).getNickname()).isEqualTo("requester"); + assertThat(response.getNext()).isNull(); + } + + @Test + @DisplayName("친구 요청이 성공적으로 전송된다.") + void should_SendFriendRequest_When_ValidRequest() { + // given + Long senderId = 1L; + Long receiverId = 2L; + Member sender = Member.builder() + .id(senderId) + .nickname("sender") + .build(); + Member receiver = Member.builder() + .id(receiverId) + .build(); + + FriendRequest savedRequest = FriendRequest.builder() + .id(1L) // ID 설정 + .sender(sender) + .receiver(receiver) + .build(); + + given(memberRepository.findById(senderId)).willReturn(Optional.of(sender)); + given(memberRepository.findById(receiverId)).willReturn(Optional.of(receiver)); + given(friendRepository.existsByMemberIdAndFriendId(senderId, receiverId)).willReturn(false); + given(friendRequestRepository.existsBySenderIdAndReceiverId(senderId, receiverId)) + .willReturn(false); + given(friendRequestRepository.save(any(FriendRequest.class))) + .willReturn(savedRequest); + + // when + friendRequestService.sendFriendRequest(senderId, receiverId); + + // then + then(friendRequestRepository).should().save(any(FriendRequest.class)); + then(notificationService).should().createAndSendNotification( + eq(receiverId), + eq(NotificationType.FRIEND_REQUEST), + eq(savedRequest.getId()), + eq(sender.getNickname()) + ); + } + + @Test + @DisplayName("이미 친구인 사용자에게 요청을 보내면 예외가 발생한다.") + void should_ThrowException_When_AlreadyFriends() { + // given + Long senderId = 1L; + Long receiverId = 2L; + + given(friendRepository.existsByMemberIdAndFriendId(senderId, receiverId)).willReturn(true); + + // when & then + assertThatThrownBy(() -> friendRequestService.sendFriendRequest(senderId, receiverId)) + .isInstanceOf(GlobalException.class) + .hasFieldOrPropertyWithValue("errorCode", ErrorCode.ALREADY_FRIEND); + } + + @Test + @DisplayName("이미 요청을 보낸 사용자에게 다시 요청을 보내면 예외가 발생한다.") + void should_ThrowException_When_RequestAlreadyExists() { + // given + Long senderId = 1L; + Long receiverId = 2L; + + given(friendRequestRepository.existsBySenderIdAndReceiverId(senderId, receiverId)) + .willReturn(true); + + // when & then + assertThatThrownBy(() -> friendRequestService.sendFriendRequest(senderId, receiverId)) + .isInstanceOf(GlobalException.class) + .hasFieldOrPropertyWithValue("errorCode", ErrorCode.FRIEND_REQUEST_ALREADY_EXISTS); + } + + @Test + @DisplayName("친구 요청이 성공적으로 수락된다.") + void should_AcceptFriendRequest_When_ValidRequest() { + // given + Long requestId = 1L; + Long receiverId = 1L; + + Member sender = Member.builder() + .id(2L) + .nickname("sender") + .build(); + Member receiver = Member.builder() + .id(receiverId) + .build(); + + FriendRequest request = FriendRequest.builder() + .id(requestId) + .sender(sender) + .receiver(receiver) + .build(); + + given(friendRequestRepository.findById(requestId)) + .willReturn(Optional.of(request)); + + // when + friendRequestService.acceptFriendRequest(requestId, receiverId); + + // then + then(friendRepository).should(times(2)).save(any(Friend.class)); + then(friendRequestRepository).should().delete(request); + then(notificationService).should().createAndSendNotification( + eq(sender.getId()), + eq(NotificationType.FRIEND_ACCEPT), + eq(receiver.getId()), + eq(receiver.getNickname()) + ); + } + + @Test + @DisplayName("다른 사용자의 친구 요청을 수락하려고 하면 예외가 발생한다.") + void should_ThrowException_When_AcceptingOtherUserRequest() { + // given + Long requestId = 1L; + Long receiverId = 1L; + Long otherUserId = 3L; + + Member sender = Member.builder() + .id(2L) + .build(); + Member receiver = Member.builder() + .id(otherUserId) // 다른 사용자 + .build(); + + FriendRequest request = FriendRequest.builder() + .id(requestId) + .sender(sender) + .receiver(receiver) + .build(); + + given(friendRequestRepository.findById(requestId)) + .willReturn(Optional.of(request)); + + // when & then + assertThatThrownBy(() -> friendRequestService.acceptFriendRequest(requestId, receiverId)) + .isInstanceOf(GlobalException.class) + .hasFieldOrPropertyWithValue("errorCode", ErrorCode.FRIEND_REQUEST_NOT_FOUND); + } + + @Test + @DisplayName("친구 요청이 성공적으로 거절된다.") + void should_RejectFriendRequest_When_ValidRequest() { + // given + Long requestId = 1L; + Long receiverId = 1L; + + Member sender = Member.builder() + .id(2L) + .build(); + Member receiver = Member.builder() + .id(receiverId) + .build(); + + FriendRequest request = FriendRequest.builder() + .id(requestId) + .sender(sender) + .receiver(receiver) + .build(); + + given(friendRequestRepository.findById(requestId)) + .willReturn(Optional.of(request)); + + // when + friendRequestService.rejectFriendRequest(requestId, receiverId); + + // then + then(friendRequestRepository).should().delete(request); + } + + @Test + @DisplayName("다른 사용자의 친구 요청을 거절하려고 하면 예외가 발생한다.") + void should_ThrowException_When_RejectingOtherUserRequest() { + // given + Long requestId = 1L; + Long receiverId = 1L; + Long otherUserId = 3L; + + Member sender = Member.builder() + .id(2L) + .build(); + Member receiver = Member.builder() + .id(otherUserId) // 다른 사용자 + .build(); + + FriendRequest request = FriendRequest.builder() + .id(requestId) + .sender(sender) + .receiver(receiver) + .build(); + + given(friendRequestRepository.findById(requestId)) + .willReturn(Optional.of(request)); + + // when & then + assertThatThrownBy(() -> friendRequestService.rejectFriendRequest(requestId, receiverId)) + .isInstanceOf(GlobalException.class) + .hasFieldOrPropertyWithValue("errorCode", ErrorCode.FRIEND_REQUEST_NOT_FOUND); + } + +} diff --git a/src/test/java/com/potatocake/everymoment/service/FriendServiceTest.java b/src/test/java/com/potatocake/everymoment/service/FriendServiceTest.java new file mode 100644 index 0000000..70209c6 --- /dev/null +++ b/src/test/java/com/potatocake/everymoment/service/FriendServiceTest.java @@ -0,0 +1,188 @@ +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.BDDMockito.given; +import static org.mockito.BDDMockito.then; + +import com.potatocake.everymoment.dto.response.FriendListResponse; +import com.potatocake.everymoment.dto.response.OneFriendDiariesResponse; +import com.potatocake.everymoment.entity.Diary; +import com.potatocake.everymoment.entity.Friend; +import com.potatocake.everymoment.entity.Member; +import com.potatocake.everymoment.exception.ErrorCode; +import com.potatocake.everymoment.exception.GlobalException; +import com.potatocake.everymoment.repository.DiaryRepository; +import com.potatocake.everymoment.repository.FileRepository; +import com.potatocake.everymoment.repository.FriendRepository; +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.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.jpa.domain.Specification; + +@ExtendWith(MockitoExtension.class) +class FriendServiceTest { + + @InjectMocks + private FriendService friendService; + + @Mock + private FriendRepository friendRepository; + + @Mock + private MemberRepository memberRepository; + + @Mock + private DiaryRepository diaryRepository; + + @Mock + private FileRepository fileRepository; + + @Test + @DisplayName("특정 친구의 일기 목록이 성공적으로 조회된다.") + void should_ReturnFriendDiaries_When_ValidRequest() { + // given + Long memberId = 1L; + Long friendId = 2L; + Member member = Member.builder() + .id(memberId) + .build(); + Member friend = Member.builder() + .id(friendId) + .build(); + Friend friendship = Friend.builder() + .member(member) + .friend(friend) + .build(); + + Diary diary = Diary.builder() + .id(1L) + .member(friend) + .content("Test diary") + .isPublic(true) + .build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(memberRepository.findById(friendId)).willReturn(Optional.of(friend)); + given(friendRepository.findByMemberAndFriend(member, friend)) + .willReturn(Optional.of(friendship)); + given(diaryRepository.findAll(any(Specification.class), any(PageRequest.class))) + .willReturn(new PageImpl<>(List.of(diary))); + + // when + OneFriendDiariesResponse response = friendService + .OneFriendDiariesResponse(memberId, friendId, null, 0, 10); + + // then + assertThat(response.getDiaries()).hasSize(1); + assertThat(response.getDiaries().get(0).getContent()).isEqualTo("Test diary"); + + then(memberRepository).should().findById(memberId); + then(memberRepository).should().findById(friendId); + then(friendRepository).should().findByMemberAndFriend(member, friend); + then(diaryRepository).should().findAll(any(Specification.class), any(PageRequest.class)); + } + + @Test + @DisplayName("친구 목록이 성공적으로 조회된다.") + void should_ReturnFriendList_When_ValidRequest() { + // given + Long memberId = 1L; + Member member = Member.builder() + .id(memberId) + .build(); + Member friend = Member.builder() + .id(2L) + .nickname("friend") + .profileImageUrl("https://example.com/profile.jpg") + .build(); + Friend friendship = Friend.builder() + .member(member) + .friend(friend) + .build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(friendRepository.findAll(any(Specification.class), any(PageRequest.class))) + .willReturn(new PageImpl<>(List.of(friendship))); + + // when + FriendListResponse response = friendService.getFriendList(memberId, null, 0, 10); + + // then + assertThat(response.getFriends()).hasSize(1); + assertThat(response.getFriends().get(0).getNickname()).isEqualTo("friend"); + + then(memberRepository).should().findById(memberId); + then(friendRepository).should().findAll(any(Specification.class), any(PageRequest.class)); + } + + @Test + @DisplayName("친구가 성공적으로 삭제된다.") + void should_DeleteFriend_When_ValidRequest() { + // given + Long memberId = 1L; + Long friendId = 2L; + Member member = Member.builder() + .id(memberId) + .build(); + Member friend = Member.builder() + .id(friendId) + .build(); + Friend friendship1 = Friend.builder() + .member(member) + .friend(friend) + .build(); + Friend friendship2 = Friend.builder() + .member(friend) + .friend(member) + .build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(memberRepository.findById(friendId)).willReturn(Optional.of(friend)); + given(friendRepository.findByMemberAndFriend(member, friend)) + .willReturn(Optional.of(friendship1)); + given(friendRepository.findByMemberAndFriend(friend, member)) + .willReturn(Optional.of(friendship2)); + + // when + friendService.deleteFriend(memberId, friendId); + + // then + then(friendRepository).should().delete(friendship1); + then(friendRepository).should().delete(friendship2); + } + + @Test + @DisplayName("존재하지 않는 친구를 삭제하려고 하면 예외가 발생한다.") + void should_ThrowException_When_FriendNotFound() { + // given + Long memberId = 1L; + Long friendId = 2L; + Member member = Member.builder() + .id(memberId) + .build(); + Member friend = Member.builder() + .id(friendId) + .build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(memberRepository.findById(friendId)).willReturn(Optional.of(friend)); + given(friendRepository.findByMemberAndFriend(member, friend)) + .willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> friendService.deleteFriend(memberId, friendId)) + .isInstanceOf(GlobalException.class) + .hasFieldOrPropertyWithValue("errorCode", ErrorCode.FRIEND_NOT_FOUND); + } + +} diff --git a/src/test/java/com/potatocake/everymoment/service/LikeServiceTest.java b/src/test/java/com/potatocake/everymoment/service/LikeServiceTest.java new file mode 100644 index 0000000..f645c71 --- /dev/null +++ b/src/test/java/com/potatocake/everymoment/service/LikeServiceTest.java @@ -0,0 +1,158 @@ +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 com.potatocake.everymoment.constant.NotificationType; +import com.potatocake.everymoment.dto.response.LikeCountResponse; +import com.potatocake.everymoment.entity.Diary; +import com.potatocake.everymoment.entity.Like; +import com.potatocake.everymoment.entity.Member; +import com.potatocake.everymoment.exception.ErrorCode; +import com.potatocake.everymoment.exception.GlobalException; +import com.potatocake.everymoment.repository.DiaryRepository; +import com.potatocake.everymoment.repository.LikeRepository; +import com.potatocake.everymoment.repository.MemberRepository; +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.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class LikeServiceTest { + + @InjectMocks + private LikeService likeService; + + @Mock + private LikeRepository likeRepository; + + @Mock + private DiaryRepository diaryRepository; + + @Mock + private MemberRepository memberRepository; + + @Mock + private NotificationService notificationService; + + @Test + @DisplayName("좋아요 수가 성공적으로 조회된다.") + void should_ReturnLikeCount_When_ValidDiaryId() { + // given + Long diaryId = 1L; + Diary diary = Diary.builder() + .id(diaryId) + .build(); + + given(diaryRepository.findById(diaryId)).willReturn(Optional.of(diary)); + given(likeRepository.countByDiary(diary)).willReturn(5L); + + // when + LikeCountResponse response = likeService.getLikeCount(diaryId); + + // then + assertThat(response.getLikeCount()).isEqualTo(5L); + then(diaryRepository).should().findById(diaryId); + then(likeRepository).should().countByDiary(diary); + } + + @Test + @DisplayName("좋아요가 성공적으로 추가된다.") + void should_AddLike_When_NotLiked() { + // given + Long memberId = 1L; + Long diaryId = 1L; + Member member = Member.builder() + .id(memberId) + .nickname("testUser") + .build(); + Member diaryOwner = Member.builder() + .id(2L) + .build(); + Diary diary = Diary.builder() + .id(diaryId) + .member(diaryOwner) + .isPublic(true) + .build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(diaryRepository.findById(diaryId)).willReturn(Optional.of(diary)); + given(likeRepository.findByMemberIdAndDiaryId(memberId, diaryId)) + .willReturn(Optional.empty()); + + // when + likeService.toggleLike(memberId, diaryId); + + // then + then(likeRepository).should().save(any(Like.class)); + then(notificationService).should().createAndSendNotification( + eq(diaryOwner.getId()), + eq(NotificationType.LIKE), + eq(diaryId), + eq(member.getNickname()) + ); + } + + @Test + @DisplayName("좋아요가 성공적으로 취소된다.") + void should_RemoveLike_When_AlreadyLiked() { + // given + Long memberId = 1L; + Long diaryId = 1L; + Member member = Member.builder() + .id(memberId) + .build(); + Diary diary = Diary.builder() + .id(diaryId) + .isPublic(true) + .build(); + Like like = Like.builder() + .id(1L) + .member(member) + .diary(diary) + .build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + given(diaryRepository.findById(diaryId)).willReturn(Optional.of(diary)); + given(likeRepository.findByMemberIdAndDiaryId(memberId, diaryId)) + .willReturn(Optional.of(like)); + + // when + likeService.toggleLike(memberId, diaryId); + + // then + then(likeRepository).should().delete(like); + } + + @Test + @DisplayName("다른 사용자의 비공개 일기에 좋아요를 누르면 예외가 발생한다.") + void should_ThrowException_When_DiaryNotPublic() { + // given + Long memberId = 1L; + Long diaryId = 1L; + Member member = Member.builder() + .id(memberId) + .build(); + Diary diary = Diary.builder() + .id(diaryId) + .isPublic(false) + .member(Member.builder().id(2L).build()) // 다른 사용자의 일기 + .build(); + + given(diaryRepository.findById(diaryId)).willReturn(Optional.of(diary)); + + // when & then + assertThatThrownBy(() -> likeService.toggleLike(memberId, diaryId)) + .isInstanceOf(GlobalException.class) + .hasFieldOrPropertyWithValue("errorCode", ErrorCode.DIARY_NOT_PUBLIC); + } + +} diff --git a/src/test/java/com/potatocake/everymoment/service/MemberServiceTest.java b/src/test/java/com/potatocake/everymoment/service/MemberServiceTest.java index df574a6..f4283fb 100644 --- a/src/test/java/com/potatocake/everymoment/service/MemberServiceTest.java +++ b/src/test/java/com/potatocake/everymoment/service/MemberServiceTest.java @@ -1,23 +1,25 @@ package com.potatocake.everymoment.service; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; -import static org.mockito.Mockito.mock; -import static org.mockito.internal.verification.VerificationModeFactory.times; +import static org.mockito.BDDMockito.willDoNothing; +import com.potatocake.everymoment.dto.response.AnonymousLoginResponse; import com.potatocake.everymoment.dto.response.FriendRequestStatus; import com.potatocake.everymoment.dto.response.MemberDetailResponse; import com.potatocake.everymoment.dto.response.MemberSearchResponse; import com.potatocake.everymoment.entity.Member; +import com.potatocake.everymoment.exception.ErrorCode; import com.potatocake.everymoment.exception.GlobalException; import com.potatocake.everymoment.repository.FriendRepository; import com.potatocake.everymoment.repository.FriendRequestRepository; import com.potatocake.everymoment.repository.MemberRepository; +import com.potatocake.everymoment.util.JwtUtil; import com.potatocake.everymoment.util.PagingUtil; import com.potatocake.everymoment.util.S3FileUploader; import java.util.List; @@ -28,9 +30,12 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.ScrollPosition; +import org.springframework.data.domain.Sort; import org.springframework.data.domain.Window; -import org.springframework.web.multipart.MultipartFile; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; @ExtendWith(MockitoExtension.class) class MemberServiceTest { @@ -42,10 +47,10 @@ class MemberServiceTest { private MemberRepository memberRepository; @Mock - private FriendRequestRepository friendRequestRepository; + private FriendRepository friendRepository; @Mock - private FriendRepository friendRepository; + private FriendRequestRepository friendRequestRepository; @Mock private PagingUtil pagingUtil; @@ -53,92 +58,180 @@ class MemberServiceTest { @Mock private S3FileUploader s3FileUploader; + @Mock + private JwtUtil jwtUtil; + + @Test + @DisplayName("익명 로그인이 성공적으로 수행된다.") + void should_LoginAnonymously_When_ValidRequest() { + // given + Long nextNumber = 1234L; + given(memberRepository.findNextAnonymousNumber()).willReturn(nextNumber); + given(memberRepository.save(any(Member.class))).willAnswer(invocation -> { + Member member = invocation.getArgument(0); + return Member.builder() + .id(1L) + .number(member.getNumber()) + .nickname(member.getNickname()) + .build(); + }); + given(jwtUtil.create(anyLong())).willReturn("jwt-token"); + + // when + AnonymousLoginResponse response = memberService.anonymousLogin(null); + + // then + assertThat(response.getNumber()).isEqualTo(nextNumber); + assertThat(response.getToken()).isEqualTo("jwt-token"); + then(memberRepository).should().findNextAnonymousNumber(); + then(memberRepository).should().save(any(Member.class)); + then(jwtUtil).should().create(anyLong()); + } + + @Test + @DisplayName("기존 번호로 익명 로그인하면 토큰이 발급된다.") + void should_ReturnToken_When_ExistingNumber() { + // given + Long memberNumber = 1234L; + Member member = Member.builder() + .id(1L) + .number(memberNumber) + .build(); + + given(memberRepository.findByNumber(memberNumber)).willReturn(Optional.of(member)); + given(jwtUtil.create(member.getId())).willReturn("jwt-token"); + + // when + AnonymousLoginResponse response = memberService.anonymousLogin(memberNumber); + + // then + assertThat(response.getToken()).isEqualTo("jwt-token"); + assertThat(response.getNumber()).isNull(); // 기존 회원이므로 번호를 반환하지 않음 + then(memberRepository).should().findByNumber(memberNumber); + then(jwtUtil).should().create(member.getId()); + } + @Test - @DisplayName("회원 목록 검색이 성공적으로 수행된다.") - void should_ReturnMemberList_When_ValidSearchConditions() { + @DisplayName("회원 검색이 성공적으로 수행된다.") + void should_SearchMembers_When_ValidRequest() { // given - String nickname = "testUser"; - Long key = 1L; + String nickname = "test"; + Long key = null; int size = 10; Long currentMemberId = 1L; - Member member1 = Member.builder().id(2L).nickname("testUser1").build(); - Member member2 = Member.builder().id(3L).nickname("testUser2").build(); - List members = List.of(member1, member2); - Window window = Window.from(members, ScrollPosition::offset, false); - - given(memberRepository.findByNicknameContaining(anyString(), any(), any())) - .willReturn(window); - given(friendRepository.existsByMemberIdAndFriendId(anyLong(), anyLong())) - .willReturn(false); + Member member1 = Member.builder() + .id(2L) + .nickname("testUser1") + .build(); + Member member2 = Member.builder() + .id(3L) + .nickname("testUser2") + .build(); + + ScrollPosition scrollPosition = ScrollPosition.offset(); + Window window = Window.from(List.of(member1, member2), i -> scrollPosition, false); + + given(pagingUtil.createScrollPosition(key)).willReturn(scrollPosition); + given(pagingUtil.createPageable(size, Sort.Direction.ASC)).willReturn(PageRequest.of(0, size)); + given(memberRepository.findByNicknameContaining(anyString(), any(), any())).willReturn(window); + given(friendRepository.existsByMemberIdAndFriendId(anyLong(), anyLong())).willReturn(false); given(friendRequestRepository.findBySenderIdAndReceiverId(anyLong(), anyLong())) .willReturn(Optional.empty()); - given(pagingUtil.getNextKey(any(), any())).willReturn(null); // when - MemberSearchResponse result = memberService.searchMembers(nickname, key, size, currentMemberId); + MemberSearchResponse response = memberService.searchMembers(nickname, key, size, currentMemberId); // then - assertThat(result).isNotNull(); - assertThat(result.getMembers()).hasSize(2); - assertThat(result.getMembers().get(0).getFriendRequestStatus()).isEqualTo(FriendRequestStatus.NONE); - assertThat(result.getMembers().get(1).getFriendRequestStatus()).isEqualTo(FriendRequestStatus.NONE); - assertThat(result.getNext()).isNull(); - - then(memberRepository).should().findByNicknameContaining(anyString(), any(), any()); - then(friendRepository).should(times(2)).existsByMemberIdAndFriendId(anyLong(), anyLong()); - then(friendRequestRepository).should(times(4)).findBySenderIdAndReceiverId(anyLong(), anyLong()); - then(pagingUtil).should().getNextKey(any(), any()); + assertThat(response.getMembers()).hasSize(2); + assertThat(response.getMembers()).extracting("nickname") + .containsExactly("testUser1", "testUser2"); + assertThat(response.getMembers()).extracting("friendRequestStatus") + .containsOnly(FriendRequestStatus.NONE); } @Test - @DisplayName("내 정보 조회가 성공적으로 수행된다.") - void should_ReturnMyInfo_When_ValidMemberId() { + @DisplayName("내 정보가 성공적으로 조회된다.") + void should_ReturnMyInfo_When_ValidId() { // given Long memberId = 1L; - Member member = Member.builder().build(); + Member member = Member.builder() + .id(memberId) + .nickname("testUser") + .profileImageUrl("https://example.com/profile.jpg") + .build(); + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); // when - MemberDetailResponse result = memberService.getMyInfo(memberId); + MemberDetailResponse response = memberService.getMyInfo(memberId); // then - assertThat(result).isNotNull(); - - then(memberRepository).should().findById(memberId); + assertThat(response.getId()).isEqualTo(memberId); + assertThat(response.getNickname()).isEqualTo("testUser"); + assertThat(response.getProfileImageUrl()).isEqualTo("https://example.com/profile.jpg"); } @Test - @DisplayName("회원 정보 수정이 성공적으로 수행된다.") + @DisplayName("회원 정보가 성공적으로 수정된다.") void should_UpdateMemberInfo_When_ValidInput() { // given Long memberId = 1L; - MultipartFile profileImage = mock(MultipartFile.class); - String nickname = "newNickname"; + Member member = Member.builder() + .id(memberId) + .nickname("oldNickname") + .profileImageUrl("https://example.com/old.jpg") + .build(); + + MockMultipartFile profileImage = new MockMultipartFile( + "profileImage", + "profile.jpg", + MediaType.IMAGE_JPEG_VALUE, + "test image".getBytes() + ); + String newNickname = "newNickname"; - Member member = Member.builder().build(); given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); - given(s3FileUploader.uploadFile(profileImage)).willReturn("profileUrl"); + given(s3FileUploader.uploadFile(profileImage)).willReturn("https://example.com/new.jpg"); // when - memberService.updateMemberInfo(memberId, profileImage, nickname); + memberService.updateMemberInfo(memberId, profileImage, newNickname); // then - assertThat(member.getNickname()).isEqualTo("newNickname"); + assertThat(member.getNickname()).isEqualTo(newNickname); + assertThat(member.getProfileImageUrl()).isEqualTo("https://example.com/new.jpg"); + } - then(memberRepository).should().findById(memberId); + @Test + @DisplayName("회원이 성공적으로 삭제된다.") + void should_DeleteMember_When_ValidId() { + // given + Long memberId = 1L; + Member member = Member.builder() + .id(memberId) + .build(); + + given(memberRepository.findById(memberId)).willReturn(Optional.of(member)); + willDoNothing().given(memberRepository).delete(member); + + // when + memberService.deleteMember(memberId); + + // then + then(memberRepository).should().delete(member); } @Test - @DisplayName("존재하지 않는 회원의 정보를 수정하려고 하면 예외가 발생한다.") + @DisplayName("존재하지 않는 회원 조회시 예외가 발생한다.") void should_ThrowException_When_MemberNotFound() { // given Long memberId = 1L; given(memberRepository.findById(memberId)).willReturn(Optional.empty()); // when & then - assertThatExceptionOfType(GlobalException.class) - .isThrownBy(() -> memberService.updateMemberInfo(memberId, null, "newNickname")); + assertThatThrownBy(() -> memberService.getMyInfo(memberId)) + .isInstanceOf(GlobalException.class) + .hasFieldOrPropertyWithValue("errorCode", ErrorCode.MEMBER_NOT_FOUND); } }