From 371cf0239f5bfca53d5abc126adefbdbdc9926ad Mon Sep 17 00:00:00 2001 From: Park Geonho Date: Sun, 3 Mar 2024 00:49:25 +0800 Subject: [PATCH 01/21] =?UTF-8?q?[test]=20Jacoco=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/gradle.yml | 10 ------ .github/workflows/tests.yml | 10 ++++++ .idea/workspace.xml | 65 ++++++++++++++++++++++++++++++------ 3 files changed, 65 insertions(+), 20 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index b031374..49ac425 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -30,16 +30,6 @@ jobs: with: arguments: build - - name: 테스트 커버리지를 PR에 코멘트로 등록합니다 - id: jacoco - uses: madrapps/jacoco-report@v1.2 - with: - title: 📝 테스트 커버리지 리포트입니다 - paths: ${{ github.workspace }}/build/reports/jacoco/test/jacocoTestReport.xml - token: ${{ secrets.GITHUB_TOKEN }} - min-coverage-overall: 50 - min-coverage-changed-files: 50 - # 3. Docker 이미지 빌드 - name: docker image build run: docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/honeycourses-backend-spring-prod . diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b19f4c1..2f41cd2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -31,3 +31,13 @@ jobs: if: ${{ always() }} # 테스트가 실패하여도 Report를 보기 위해 `always`로 설정 with: files: build/test-results/**/*.xml + + - name: 테스트 커버리지를 PR에 코멘트로 등록합니다 + id: jacoco + uses: madrapps/jacoco-report@v1.2 + with: + title: 📝 테스트 커버리지 리포트입니다 + paths: ${{ github.workspace }}/build/reports/jacoco/test/jacocoTestReport.xml + token: ${{ secrets.GITHUB_TOKEN }} + min-coverage-overall: 50 + min-coverage-changed-files: 50 diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 2473420..745a65f 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -4,10 +4,7 @@ - - - @@ -674,8 +716,11 @@ + - From cd4b2e2bcc6582c0a7fee644860a399236fa7141 Mon Sep 17 00:00:00 2001 From: Park Geonho Date: Sun, 3 Mar 2024 01:28:58 +0800 Subject: [PATCH 02/21] =?UTF-8?q?[refactor]=20Post=20dto=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=ED=95=84=EB=93=9C=20=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SnakeCase 에서 CamelCase 로 변경했어요. --- .idea/workspace.xml | 7 ++- src/docs/asciidoc/index.adoc | 2 +- .../api/config/SecurityConfig.java | 2 +- .../api/post/dto/NewPostRequest.java | 17 +++--- .../api/post/dto/NewPostResponse.java | 10 ++-- .../api/post/dto/PostDeleteResponse.java | 9 ++-- .../api/post/dto/PostLikeResponse.java | 9 ++-- .../api/post/dto/PostResponse.java | 53 ++++++++++--------- .../api/post/dto/PostUpdateRequest.java | 13 +++-- .../api/post/service/PostService.java | 19 ++++--- .../post/controller/PostControllerTest.java | 30 +++++------ .../api/post/service/PostServiceTest.java | 36 ++++++------- .../api/util/ResponseFixture.java | 32 +++++------ 13 files changed, 129 insertions(+), 110 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 745a65f..53606e2 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -4,7 +4,10 @@ - + + + + + + + + + + + + + + - + + + - { + "keyToString": { + "Gradle.Build honeycourses-backend-spring.executor": "Run", + "Gradle.CommentControllerTest.addComment.executor": "Run", + "Gradle.CommentControllerTest.addReply.executor": "Run", + "Gradle.CommentControllerTest.deleteComment.executor": "Run", + "Gradle.CommentControllerTest.executor": "Run", + "Gradle.CommentControllerTest.findAllCommentsByPostId.executor": "Run", + "Gradle.CommentControllerTest.updateComment.executor": "Run", + "Gradle.CommentControllerTest.updateComment_exception_noAuth.executor": "Run", + "Gradle.CommentControllerTest.updateComment_exception_noContent.executor": "Run", + "Gradle.CommentServiceTest.addReply.executor": "Run", + "Gradle.CommentServiceTest.addReply_exception_depth.executor": "Run", + "Gradle.CommentServiceTest.deletePrentAndReply.executor": "Run", + "Gradle.CommentServiceTest.deleteReply.executor": "Run", + "Gradle.CommentServiceTest.deleteReply_keep_parent.executor": "Run", + "Gradle.CommentServiceTest.executor": "Run", + "Gradle.CommentServiceTest.likeComment_up.executor": "Run", + "Gradle.CommentServiceTest.updateComment.executor": "Run", + "Gradle.CommentServiceTest.updateComment_exception_noAuth.executor": "Run", + "Gradle.CourseServiceTest.executor": "Debug", + "Gradle.PostControllerTest.addPost.executor": "Run", + "Gradle.PostControllerTest.addPost_exception_noTitle.executor": "Run", + "Gradle.PostControllerTest.deletePost.executor": "Run", + "Gradle.PostControllerTest.executor": "Run", + "Gradle.PostControllerTest.findAllPosts.executor": "Run", + "Gradle.PostControllerTest.findMyPosts.executor": "Run", + "Gradle.PostControllerTest.findPost.executor": "Run", + "Gradle.PostControllerTest.findPost_exception_invalidId.executor": "Run", + "Gradle.PostControllerTest.findPosts.executor": "Run", + "Gradle.PostControllerTest.findPostsByCategory.executor": "Run", + "Gradle.PostControllerTest.likePost_up.executor": "Run", + "Gradle.PostControllerTest.updatePost.executor": "Run", + "Gradle.PostServiceTest.addPost_exception_invalid_category.executor": "Run", + "Gradle.PostServiceTest.deletePost.executor": "Run", + "Gradle.PostServiceTest.executor": "Run", + "Gradle.PostServiceTest.findAllPosts_login.executor": "Run", + "Gradle.PostServiceTest.findMyPosts.executor": "Run", + "Gradle.PostServiceTest.findPosts_category_free.executor": "Run", + "Gradle.PostServiceTest.likePost.executor": "Run", + "Gradle.PostServiceTest.likePost_down.executor": "Run", + "Gradle.PostServiceTest.likePost_up.executor": "Debug", + "Gradle.PostServiceTest.updatePost.executor": "Run", + "Gradle.ReportControllerTest.reportComment.executor": "Run", + "Gradle.ReportControllerTest.reportPost (1).executor": "Run", + "Gradle.ReportControllerTest.reportPost.executor": "Run", + "Gradle.ReportServiceTest.reportComment.executor": "Run", + "Gradle.ReportServiceTest.reportComment_exception_duplicated.executor": "Run", + "Gradle.ReportServiceTest.reportPost.executor": "Run", + "Gradle.ReportServiceTest.reportPost_exception_duplicated.executor": "Run", + "Gradle.ReportServiceTest.reportPost_exception_invalidId.executor": "Run", + "Gradle.ReviewControllerTest.findAllReviewsByCourseId_exception_accessDenied.executor": "Run", + "Gradle.ReviewControllerTest.findAllReviewsByCourseId_success.executor": "Run", + "Gradle.ReviewServiceTest.canAccessReviews_false_noReview.executor": "Run", + "Gradle.ReviewServiceTest.canAccessReviews_false_sixMonths.executor": "Run", + "Gradle.ReviewServiceTest.canAccessReviews_true.executor": "Run", + "Notification.DisplayName-DoNotAsk-Lombok plugin": "Lombok integration problem", + "Notification.DoNotAsk-Lombok plugin": "true", + "RequestMappingsPanelOrder0": "0", + "RequestMappingsPanelOrder1": "1", + "RequestMappingsPanelWidth0": "75", + "RequestMappingsPanelWidth1": "75", + "RunOnceActivity.OpenProjectViewOnStart": "true", + "RunOnceActivity.ShowReadmeOnStart": "true", + "SHARE_PROJECT_CONFIGURATION_FILES": "true", + "Shell Script.gradlew.executor": "Run", + "Spring Boot.HoneycoursesBackend.executor": "Run", + "WebServerToolWindowFactoryState": "false", + "com.intellij.testIntegration.createTest.CreateTestDialog.defaultLibrary": "JUnit5", + "com.intellij.testIntegration.createTest.CreateTestDialog.defaultLibrarySuperClass.JUnit5": "", + "create.test.in.the.same.root": "true", + "extract.method.default.visibility": "private", + "git-widget-placeholder": "HC-204-refactoring", + "ignore.virus.scanning.warn.message": "true", + "kotlin-language-version-configured": "true", + "last_opened_file_path": "/Users/pagh/Desktop/honeycourses-backend-spring/build.gradle", + "node.js.detected.package.eslint": "true", + "node.js.detected.package.tslint": "true", + "node.js.selected.package.eslint": "(autodetect)", + "node.js.selected.package.tslint": "(autodetect)", + "nodejs_package_manager_path": "npm", + "project.structure.last.edited": "Modules", + "project.structure.proportion": "0.15", + "project.structure.side.proportion": "0.2", + "settings.editor.selected.configurable": "reference.settingsdialog.project.gradle", + "vue.rearranger.settings.migration": "true" }, - "keyToStringList": { - "DatabaseDriversLRU": [ - "mysql" + "keyToStringList": { + "DatabaseDriversLRU": [ + "mysql" ] } -}]]> +} @@ -367,6 +379,13 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 862b653..8ab989a 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -4,22 +4,7 @@ - - - - - @@ -742,7 +759,12 @@ - diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index 2e08572..bf78468 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -66,7 +66,7 @@ operation::reviews/delete/fail/noAuth[snippets='http-request,http-response'] ==== 성공 operation::post/find/all/success[snippets='http-request,http-response'] -=== 특정 카테고리 게시글 보기 (GET /posts?category={category}&page=1) +=== 특정 카테고리 게시글 보기 (GET /posts?page=1&category=free) 유효한 category 값: free(자유), question(질문), trade(중고거래), offer(구인), notice(공지) ==== 성공 diff --git a/src/main/java/org/wooriverygood/api/post/api/PostApi.java b/src/main/java/org/wooriverygood/api/post/api/PostApi.java index bb4f9bf..f0e0e79 100644 --- a/src/main/java/org/wooriverygood/api/post/api/PostApi.java +++ b/src/main/java/org/wooriverygood/api/post/api/PostApi.java @@ -33,7 +33,7 @@ public class PostApi { @GetMapping public ResponseEntity findPosts(@Login AuthInfo authInfo, - @PathVariable(required = false, value = "category") String category, + @RequestParam(required = false, defaultValue = "", value = "category") String category, @RequestParam(required = false, defaultValue = "0", value = "page") int pageNo) { Pageable pageable = PageRequest.of(pageNo, PAGE_SIZE); PostsResponse response = postFindService.findPosts(authInfo, pageable, category); diff --git a/src/main/java/org/wooriverygood/api/post/application/PostFindService.java b/src/main/java/org/wooriverygood/api/post/application/PostFindService.java index ed40442..a213f62 100644 --- a/src/main/java/org/wooriverygood/api/post/application/PostFindService.java +++ b/src/main/java/org/wooriverygood/api/post/application/PostFindService.java @@ -32,7 +32,7 @@ public PostsResponse findPosts(AuthInfo authInfo, Pageable pageable, String post } private Page findPostsPage(Pageable pageable, String postCategory) { - if (postCategory == null || postCategory.isEmpty()) + if (postCategory.isEmpty()) return postRepository.findAllByOrderByIdDesc(pageable); PostCategory category = PostCategory.parse(postCategory); return postRepository.findAllByCategoryOrderByIdDesc(category, pageable); diff --git a/src/test/java/org/wooriverygood/api/post/api/PostApiTest.java b/src/test/java/org/wooriverygood/api/post/api/PostApiTest.java index 785be89..96fe8ac 100644 --- a/src/test/java/org/wooriverygood/api/post/api/PostApiTest.java +++ b/src/test/java/org/wooriverygood/api/post/api/PostApiTest.java @@ -13,6 +13,10 @@ import org.wooriverygood.api.util.ApiTest; import org.wooriverygood.api.util.ResponseFixture; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; @@ -38,8 +42,30 @@ void findPosts() { @Test @DisplayName("자유 카테고리의 2번째 페이지를 반환한다.") void findPostsByCategory() { + List responses = new ArrayList<>(); + int bound = Math.min(39 - 2 * 10 + 1, 10); + int start = 2 * 10 + 1; + for (long i = start; i < start + bound; i++) + responses.add(PostResponse.builder() + .postId(i) + .postTitle("title_" + i) + .postCategory("자유") + .postContent("content_" + i) + .postAuthor("author") + .postComments((int) (Math.random() * 100)) + .postLikes((int) (Math.random() * 100)) + .postTime(LocalDateTime.now()) + .liked(i % 6 == 0) + .updated(i % 9 == 0) + .reported(false) + .build()); + when(postFindService.findPosts(any(AuthInfo.class), any(Pageable.class), anyString())) - .thenReturn(ResponseFixture.postsResponse(2, 39, "자유", testAuthInfo)); + .thenReturn(PostsResponse.builder() + .posts(responses) + .totalPostCount(39) + .totalPageCount((int) Math.ceil((double) 39 / 10)) + .build()); restDocs .contentType(MediaType.APPLICATION_JSON_VALUE) diff --git a/src/test/java/org/wooriverygood/api/post/application/PostFindServiceTest.java b/src/test/java/org/wooriverygood/api/post/application/PostFindServiceTest.java index ce83ac9..0c5c415 100644 --- a/src/test/java/org/wooriverygood/api/post/application/PostFindServiceTest.java +++ b/src/test/java/org/wooriverygood/api/post/application/PostFindServiceTest.java @@ -86,7 +86,7 @@ void findPosts_login() { Mockito.when(postRepository.findAllByOrderByIdDesc(any(PageRequest.class))) .thenReturn(page); - PostsResponse response = postFindService.findPosts(authInfo, pageable, null); + PostsResponse response = postFindService.findPosts(authInfo, pageable, ""); assertThat(response.getPosts().size()).isEqualTo(10); assertThat(response.getTotalPageCount()).isEqualTo(3); From a4cef39d94c5fd20f4ad21296ba2f68761543a49 Mon Sep 17 00:00:00 2001 From: Park Geonho Date: Mon, 1 Apr 2024 03:34:29 +0800 Subject: [PATCH 08/21] =?UTF-8?q?[feat]=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D/=EC=83=81=EC=84=B8=20=EB=82=B4=EC=9A=A9=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 추가 Review 접근 검증 서비스 분리 --- .../java/org/wooriverygood/api/SampleApi.java | 13 ++++ .../api/comment/domain/Comment.java | 12 ++- ...thenticationPrincipalArgumentResolver.java | 9 ++- .../api/global/config/SecurityConfig.java | 2 +- .../exception/AuthorizationException.java | 2 +- .../wooriverygood/api/post/api/PostApi.java | 24 +++--- .../api/post/application/PostFindService.java | 5 +- .../wooriverygood/api/post/domain/Post.java | 28 ++++--- .../api/post/domain/PostLike.java | 7 +- .../wooriverygood/api/post/domain/Title.java | 13 ++-- .../api/post/dto/PostDeleteResponse.java | 19 ----- .../api/post/dto/PostDetailResponse.java | 73 +++++++++++++++++++ .../api/post/dto/PostResponse.java | 15 +--- .../api/post/dto/PostUpdateResponse.java | 16 ---- .../ReviewApi.java} | 42 +++++------ .../ReviewService.java | 17 +---- .../ReviewValidateAccessService.java | 31 ++++++++ .../api/review/domain/Review.java | 2 +- .../ReviewAccessDeniedException.java | 2 +- .../exception/ReviewNotFoundException.java | 2 +- .../comment/controller/CommentApiTest.java | 2 +- .../comment/service/CommentServiceTest.java | 2 +- .../api/post/api/PostApiTest.java | 16 +++- .../application/PostDeleteServiceTest.java | 2 +- .../post/application/PostFindServiceTest.java | 16 ++-- .../application/PostUpdateServiceTest.java | 2 +- .../{controller => api}/ReviewApiTest.java | 51 +++++++------ .../ReviewServiceTest.java | 31 +------- .../ReviewValidateAccessServiceTest.java | 71 ++++++++++++++++++ .../org/wooriverygood/api/util/ApiTest.java | 10 ++- .../api/util/ResponseFixture.java | 4 - 31 files changed, 333 insertions(+), 208 deletions(-) create mode 100644 src/main/java/org/wooriverygood/api/SampleApi.java rename src/main/java/org/wooriverygood/api/{advice => global/error}/exception/AuthorizationException.java (83%) delete mode 100644 src/main/java/org/wooriverygood/api/post/dto/PostDeleteResponse.java create mode 100644 src/main/java/org/wooriverygood/api/post/dto/PostDetailResponse.java delete mode 100644 src/main/java/org/wooriverygood/api/post/dto/PostUpdateResponse.java rename src/main/java/org/wooriverygood/api/review/{controller/ReviewController.java => api/ReviewApi.java} (68%) rename src/main/java/org/wooriverygood/api/review/{service => application}/ReviewService.java (90%) create mode 100644 src/main/java/org/wooriverygood/api/review/application/ReviewValidateAccessService.java rename src/main/java/org/wooriverygood/api/{advice => review}/exception/ReviewAccessDeniedException.java (87%) rename src/main/java/org/wooriverygood/api/{advice => review}/exception/ReviewNotFoundException.java (85%) rename src/test/java/org/wooriverygood/api/review/{controller => api}/ReviewApiTest.java (85%) rename src/test/java/org/wooriverygood/api/review/{service => application}/ReviewServiceTest.java (87%) create mode 100644 src/test/java/org/wooriverygood/api/review/application/ReviewValidateAccessServiceTest.java diff --git a/src/main/java/org/wooriverygood/api/SampleApi.java b/src/main/java/org/wooriverygood/api/SampleApi.java new file mode 100644 index 0000000..6996e38 --- /dev/null +++ b/src/main/java/org/wooriverygood/api/SampleApi.java @@ -0,0 +1,13 @@ +package org.wooriverygood.api; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class SampleApi { + + public ResponseEntity sample() { + return ResponseEntity.ok("api is online"); + } + +} diff --git a/src/main/java/org/wooriverygood/api/comment/domain/Comment.java b/src/main/java/org/wooriverygood/api/comment/domain/Comment.java index 0d43326..436d88e 100644 --- a/src/main/java/org/wooriverygood/api/comment/domain/Comment.java +++ b/src/main/java/org/wooriverygood/api/comment/domain/Comment.java @@ -1,13 +1,11 @@ package org.wooriverygood.api.comment.domain; import jakarta.persistence.*; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; import org.hibernate.annotations.ColumnDefault; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; -import org.wooriverygood.api.advice.exception.AuthorizationException; +import org.wooriverygood.api.global.error.exception.AuthorizationException; import org.wooriverygood.api.post.domain.Post; import org.wooriverygood.api.report.domain.CommentReport; @@ -20,7 +18,7 @@ @Table(name = "comments") @Getter @EntityListeners(AuditingEntityListener.class) -@NoArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class Comment { @Id @@ -46,14 +44,14 @@ public class Comment { private Post post; @OneToMany(mappedBy = "comment", cascade = CascadeType.ALL, orphanRemoval = true) - private List commentLikes; + private List commentLikes = new ArrayList<>(); @Column(name = "like_count") @ColumnDefault("0") private int likeCount; @OneToMany(mappedBy = "comment") - private List reports; + private List reports = new ArrayList<>(); @Column(name = "report_count") @ColumnDefault("0") diff --git a/src/main/java/org/wooriverygood/api/global/auth/AuthenticationPrincipalArgumentResolver.java b/src/main/java/org/wooriverygood/api/global/auth/AuthenticationPrincipalArgumentResolver.java index 0e9d82a..a3a1e77 100644 --- a/src/main/java/org/wooriverygood/api/global/auth/AuthenticationPrincipalArgumentResolver.java +++ b/src/main/java/org/wooriverygood/api/global/auth/AuthenticationPrincipalArgumentResolver.java @@ -17,10 +17,15 @@ public boolean supportsParameter(MethodParameter parameter) { } @Override - public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) { + public Object resolveArgument(MethodParameter parameter, + ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, + WebDataBinderFactory binderFactory) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication != null && authentication.getPrincipal() instanceof Jwt jwt) return createAuthInfo(jwt); + if (authentication != null && authentication.getPrincipal() instanceof Jwt jwt) + return createAuthInfo(jwt); + return AuthInfo.builder().build(); } diff --git a/src/main/java/org/wooriverygood/api/global/config/SecurityConfig.java b/src/main/java/org/wooriverygood/api/global/config/SecurityConfig.java index 20c00b0..c1118a4 100644 --- a/src/main/java/org/wooriverygood/api/global/config/SecurityConfig.java +++ b/src/main/java/org/wooriverygood/api/global/config/SecurityConfig.java @@ -36,7 +36,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .authorizeHttpRequests(authorizationManagerRequestMatcherRegistry -> authorizationManagerRequestMatcherRegistry .requestMatchers(HttpMethod.OPTIONS).permitAll() - .requestMatchers("/*", "/docs/**", "/docs/index.html").permitAll() // 권한이 필요없는 엔드 포인트, 루트와 API 명세 엔드포인트 열어둠 + .requestMatchers("/*", "/docs/**", "/docs/index.html").permitAll() .anyRequest().authenticated()) .oauth2ResourceServer(httpSecurityOAuth2ResourceServerConfigurer -> httpSecurityOAuth2ResourceServerConfigurer.jwt(jwtConfigurer -> diff --git a/src/main/java/org/wooriverygood/api/advice/exception/AuthorizationException.java b/src/main/java/org/wooriverygood/api/global/error/exception/AuthorizationException.java similarity index 83% rename from src/main/java/org/wooriverygood/api/advice/exception/AuthorizationException.java rename to src/main/java/org/wooriverygood/api/global/error/exception/AuthorizationException.java index d061b64..4fa6e29 100644 --- a/src/main/java/org/wooriverygood/api/advice/exception/AuthorizationException.java +++ b/src/main/java/org/wooriverygood/api/global/error/exception/AuthorizationException.java @@ -1,4 +1,4 @@ -package org.wooriverygood.api.advice.exception; +package org.wooriverygood.api.global.error.exception; import org.wooriverygood.api.global.error.exception.ForbiddenException; diff --git a/src/main/java/org/wooriverygood/api/post/api/PostApi.java b/src/main/java/org/wooriverygood/api/post/api/PostApi.java index f0e0e79..9bce494 100644 --- a/src/main/java/org/wooriverygood/api/post/api/PostApi.java +++ b/src/main/java/org/wooriverygood/api/post/api/PostApi.java @@ -33,18 +33,18 @@ public class PostApi { @GetMapping public ResponseEntity findPosts(@Login AuthInfo authInfo, - @RequestParam(required = false, defaultValue = "", value = "category") String category, - @RequestParam(required = false, defaultValue = "0", value = "page") int pageNo) { + @RequestParam(required = false, defaultValue = "0", value = "page") int pageNo, + @RequestParam(required = false, defaultValue = "", value = "category") String category) { Pageable pageable = PageRequest.of(pageNo, PAGE_SIZE); PostsResponse response = postFindService.findPosts(authInfo, pageable, category); return ResponseEntity.ok(response); } @GetMapping("/{id}") - public ResponseEntity findPostById(@PathVariable("id") Long postId, - @Login AuthInfo authInfo) { - PostResponse postResponse = postFindService.findPostById(postId, authInfo); - return ResponseEntity.ok(postResponse); + public ResponseEntity findPostById(@PathVariable("id") Long postId, + @Login AuthInfo authInfo) { + PostDetailResponse postDetailResponse = postFindService.findPostById(postId, authInfo); + return ResponseEntity.ok(postDetailResponse); } @PostMapping @@ -63,22 +63,22 @@ public ResponseEntity findMyPosts(@Login AuthInfo authInfo, } @PutMapping("/{id}/like") - public ResponseEntity togglePostLike(@PathVariable("id") long postId, - @Login AuthInfo authInfo) { + public ResponseEntity togglePostLike(@PathVariable("id") Long postId, + @Login AuthInfo authInfo) { PostLikeResponse response = postLikeToggleService.togglePostLike(postId, authInfo); return ResponseEntity.ok(response); } @PutMapping("/{id}") - public ResponseEntity updatePost(@PathVariable("id") long postId, - @Valid @RequestBody PostUpdateRequest postUpdateRequest, - @Login AuthInfo authInfo) { + public ResponseEntity updatePost(@PathVariable("id") Long postId, + @Valid @RequestBody PostUpdateRequest postUpdateRequest, + @Login AuthInfo authInfo) { postUpdateService.updatePost(postId, postUpdateRequest, authInfo); return ResponseEntity.noContent().build(); } @DeleteMapping("/{id}") - public ResponseEntity deletePost(@PathVariable("id") long postId, + public ResponseEntity deletePost(@PathVariable("id") Long postId, @Login AuthInfo authInfo) { postDeleteService.deletePost(authInfo, postId); return ResponseEntity.noContent().build(); diff --git a/src/main/java/org/wooriverygood/api/post/application/PostFindService.java b/src/main/java/org/wooriverygood/api/post/application/PostFindService.java index a213f62..b60439a 100644 --- a/src/main/java/org/wooriverygood/api/post/application/PostFindService.java +++ b/src/main/java/org/wooriverygood/api/post/application/PostFindService.java @@ -7,6 +7,7 @@ import org.springframework.transaction.annotation.Transactional; import org.wooriverygood.api.post.domain.Post; import org.wooriverygood.api.post.domain.PostCategory; +import org.wooriverygood.api.post.dto.PostDetailResponse; import org.wooriverygood.api.post.dto.PostResponse; import org.wooriverygood.api.post.dto.PostsResponse; import org.wooriverygood.api.post.exception.PostNotFoundException; @@ -39,12 +40,12 @@ private Page findPostsPage(Pageable pageable, String postCategory) { } @Transactional - public PostResponse findPostById(Long postId, AuthInfo authInfo) { + public PostDetailResponse findPostById(long postId, AuthInfo authInfo) { Post post = postRepository.findById(postId) .orElseThrow(PostNotFoundException::new); boolean liked = postLikeRepository.existsByPostAndUsername(post, authInfo.getUsername()); postRepository.increaseViewCount(postId); - return PostResponse.of(post, liked); + return PostDetailResponse.of(post, liked); } public PostsResponse findMyPosts(AuthInfo authInfo, Pageable pageable) { diff --git a/src/main/java/org/wooriverygood/api/post/domain/Post.java b/src/main/java/org/wooriverygood/api/post/domain/Post.java index d11c6af..e9a4f06 100644 --- a/src/main/java/org/wooriverygood/api/post/domain/Post.java +++ b/src/main/java/org/wooriverygood/api/post/domain/Post.java @@ -1,18 +1,16 @@ package org.wooriverygood.api.post.domain; import jakarta.persistence.*; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; import org.hibernate.annotations.ColumnDefault; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; -import org.wooriverygood.api.advice.exception.AuthorizationException; +import org.wooriverygood.api.global.error.exception.AuthorizationException; import org.wooriverygood.api.comment.domain.Comment; import org.wooriverygood.api.report.domain.PostReport; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.List; @Entity @@ -40,18 +38,18 @@ public class Post { @Column(name = "post_author", length = 1000) private String author; - @OneToMany(mappedBy = "post") - private List comments; + @OneToMany(mappedBy = "post", fetch = FetchType.EAGER) + private List comments = new ArrayList<>(); @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true) - private List postLikes; + private List postLikes = new ArrayList<>(); @Column(name = "like_count") @ColumnDefault("0") private int likeCount; @OneToMany(mappedBy = "post") - private List reports; + private List reports = new ArrayList<>(); @Column(name = "report_count") @ColumnDefault("0") @@ -122,4 +120,16 @@ public boolean hasReportByUser(String username) { return false; } + public String getTitle() { + return isReportedTooMuch() ? null : title.getValue(); + } + + public String getContent() { + return isReportedTooMuch() ? null : content.getValue(); + } + + public boolean isReportedTooMuch() { + return reportCount >= 5; + } + } diff --git a/src/main/java/org/wooriverygood/api/post/domain/PostLike.java b/src/main/java/org/wooriverygood/api/post/domain/PostLike.java index 728decc..d1c9131 100644 --- a/src/main/java/org/wooriverygood/api/post/domain/PostLike.java +++ b/src/main/java/org/wooriverygood/api/post/domain/PostLike.java @@ -1,14 +1,12 @@ package org.wooriverygood.api.post.domain; import jakarta.persistence.*; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; @Entity @Table(name = "postLikes") @Getter -@NoArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class PostLike { @Id @@ -29,6 +27,7 @@ public PostLike(Long id, Post post, String username) { this.post = post; this.username = username; } + public void delete() { this.post = null; } diff --git a/src/main/java/org/wooriverygood/api/post/domain/Title.java b/src/main/java/org/wooriverygood/api/post/domain/Title.java index 648efa5..77070e6 100644 --- a/src/main/java/org/wooriverygood/api/post/domain/Title.java +++ b/src/main/java/org/wooriverygood/api/post/domain/Title.java @@ -2,12 +2,15 @@ import jakarta.persistence.Column; import jakarta.persistence.Embeddable; +import lombok.AccessLevel; import lombok.Getter; +import lombok.NoArgsConstructor; import org.wooriverygood.api.post.exception.InvalidPostContentException; import org.wooriverygood.api.post.exception.InvalidPostTitleException; @Getter @Embeddable +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class Title { private static final int MAX_TITLE_LENGTH = 45; @@ -16,21 +19,17 @@ public class Title { private String value; - protected Title() { - } - public Title(String value) { validate(value); this.value = value; } private void validate(String value) { - if (value == null || value.isBlank()) { + if (value == null || value.isBlank()) throw new InvalidPostTitleException(); - } - if (value.length() > MAX_TITLE_LENGTH) { + + if (value.length() > MAX_TITLE_LENGTH) throw new InvalidPostContentException(); - } } } diff --git a/src/main/java/org/wooriverygood/api/post/dto/PostDeleteResponse.java b/src/main/java/org/wooriverygood/api/post/dto/PostDeleteResponse.java deleted file mode 100644 index fe5331e..0000000 --- a/src/main/java/org/wooriverygood/api/post/dto/PostDeleteResponse.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.wooriverygood.api.post.dto; - -import com.fasterxml.jackson.databind.PropertyNamingStrategies; -import com.fasterxml.jackson.databind.annotation.JsonNaming; -import lombok.Builder; -import lombok.Getter; - -@Getter -@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) -public class PostDeleteResponse { - - private final Long postId; - - @Builder - public PostDeleteResponse(Long postId) { - this.postId = postId; - } - -} diff --git a/src/main/java/org/wooriverygood/api/post/dto/PostDetailResponse.java b/src/main/java/org/wooriverygood/api/post/dto/PostDetailResponse.java new file mode 100644 index 0000000..0f28679 --- /dev/null +++ b/src/main/java/org/wooriverygood/api/post/dto/PostDetailResponse.java @@ -0,0 +1,73 @@ +package org.wooriverygood.api.post.dto; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Builder; +import lombok.Getter; +import org.wooriverygood.api.post.domain.Post; + +import java.time.LocalDateTime; + +@Getter +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +public class PostDetailResponse { + + private final Long postId; + + private final String postTitle; + + private final String postContent; + + private final String postCategory; + + private final String postAuthor; + + private final int postComments; + + private final int postLikes; + + private final LocalDateTime postTime; + + private final boolean liked; + + private final boolean updated; + + private final boolean reported; + + private final int viewCount; + + + @Builder + public PostDetailResponse(Long postId, String postTitle, String postContent, String postCategory, String postAuthor, int postComments, int postLikes, LocalDateTime postTime, boolean liked, boolean updated, boolean reported, int viewCount) { + this.postId = postId; + this.postTitle = postTitle; + this.postContent = postContent; + this.postCategory = postCategory; + this.postAuthor = postAuthor; + this.postComments = postComments; + this.postLikes = postLikes; + this.postTime = postTime; + this.liked = liked; + this.updated = updated; + this.reported = reported; + this.viewCount = viewCount; + } + + public static PostDetailResponse of(Post post, boolean liked) { + return PostDetailResponse.builder() + .postId(post.getId()) + .postTitle(post.getTitle()) + .postContent(post.getContent()) + .postCategory(post.getCategory().getValue()) + .postAuthor(post.getAuthor()) + .postComments(post.getCommentCount()) + .postLikes(post.getLikeCount()) + .postTime(post.getCreatedAt()) + .liked(liked) + .updated(post.isUpdated()) + .reported(post.isReportedTooMuch()) + .viewCount(post.getViewCount()) + .build(); + } + +} diff --git a/src/main/java/org/wooriverygood/api/post/dto/PostResponse.java b/src/main/java/org/wooriverygood/api/post/dto/PostResponse.java index 29ec991..a05a03f 100644 --- a/src/main/java/org/wooriverygood/api/post/dto/PostResponse.java +++ b/src/main/java/org/wooriverygood/api/post/dto/PostResponse.java @@ -16,12 +16,8 @@ public class PostResponse { private final String postTitle; - private final String postContent; - private final String postCategory; - private final String postAuthor; - private final int postComments; private final int postLikes; @@ -38,12 +34,10 @@ public class PostResponse { @Builder - public PostResponse(Long postId, String postTitle, String postContent, String postCategory, String postAuthor, int postComments, int postLikes, LocalDateTime postTime, boolean liked, boolean updated, boolean reported, int viewCount) { + public PostResponse(Long postId, String postTitle, String postCategory, int postComments, int postLikes, LocalDateTime postTime, boolean liked, boolean updated, boolean reported, int viewCount) { this.postId = postId; this.postTitle = postTitle; - this.postContent = postContent; this.postCategory = postCategory; - this.postAuthor = postAuthor; this.postComments = postComments; this.postLikes = postLikes; this.postTime = postTime; @@ -54,19 +48,16 @@ public PostResponse(Long postId, String postTitle, String postContent, String po } public static PostResponse of(Post post, boolean liked) { - boolean reported = post.getReportCount() >= 5; return PostResponse.builder() .postId(post.getId()) - .postTitle(reported ? null : post.getTitle().getValue()) - .postContent(reported ? null : post.getContent().getValue()) + .postTitle(post.getTitle()) .postCategory(post.getCategory().getValue()) - .postAuthor(post.getAuthor()) .postComments(post.getCommentCount()) .postLikes(post.getLikeCount()) .postTime(post.getCreatedAt()) .liked(liked) .updated(post.isUpdated()) - .reported(reported) + .reported(post.isReportedTooMuch()) .viewCount(post.getViewCount()) .build(); } diff --git a/src/main/java/org/wooriverygood/api/post/dto/PostUpdateResponse.java b/src/main/java/org/wooriverygood/api/post/dto/PostUpdateResponse.java deleted file mode 100644 index 8556b4d..0000000 --- a/src/main/java/org/wooriverygood/api/post/dto/PostUpdateResponse.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.wooriverygood.api.post.dto; - -import lombok.Builder; -import lombok.Getter; - -@Getter -public class PostUpdateResponse { - - private final Long post_id; - - - @Builder - public PostUpdateResponse(Long post_id) { - this.post_id = post_id; - } -} diff --git a/src/main/java/org/wooriverygood/api/review/controller/ReviewController.java b/src/main/java/org/wooriverygood/api/review/api/ReviewApi.java similarity index 68% rename from src/main/java/org/wooriverygood/api/review/controller/ReviewController.java rename to src/main/java/org/wooriverygood/api/review/api/ReviewApi.java index c11082a..7587094 100644 --- a/src/main/java/org/wooriverygood/api/review/controller/ReviewController.java +++ b/src/main/java/org/wooriverygood/api/review/api/ReviewApi.java @@ -1,58 +1,55 @@ -package org.wooriverygood.api.review.controller; +package org.wooriverygood.api.review.api; import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import org.wooriverygood.api.advice.exception.ReviewAccessDeniedException; +import org.wooriverygood.api.review.application.ReviewValidateAccessService; import org.wooriverygood.api.review.dto.*; -import org.wooriverygood.api.review.service.ReviewService; +import org.wooriverygood.api.review.application.ReviewService; import org.wooriverygood.api.global.auth.AuthInfo; import org.wooriverygood.api.global.auth.Login; import java.util.List; @RestController -public class ReviewController { +@RequestMapping("/courses") +@RequiredArgsConstructor +public class ReviewApi { + private final ReviewService reviewService; - public ReviewController(ReviewService reviewService) { - this.reviewService = reviewService; - } + private final ReviewValidateAccessService reviewValidateAccessService; - @GetMapping("/") - public ResponseEntity apiOnline() { - return ResponseEntity.ok("API Online!"); - } - @GetMapping("/courses/{id}/reviews") - public ResponseEntity> findAllReviewsByCourseId(@PathVariable("id") Long courseId, @Login AuthInfo authInfo) { - if (!reviewService.canAccessReviews(authInfo)) { - throw new ReviewAccessDeniedException(); - } + @GetMapping("/{id}/reviews") + public ResponseEntity> findAllReviewsByCourseId(@PathVariable("id") Long courseId, + @Login AuthInfo authInfo) { + reviewValidateAccessService.validateReviewAccess(authInfo); List reviews = reviewService.findAllReviewsByCourseId(courseId, authInfo); return ResponseEntity.ok(reviews); } - @PostMapping("/courses/{id}/reviews") + @PostMapping("/{id}/reviews") public ResponseEntity addReview(@PathVariable("id") Long courseId, @Login AuthInfo authInfo, @Valid @RequestBody NewReviewRequest newReviewRequest) { NewReviewResponse response = reviewService.addReview(authInfo, courseId, newReviewRequest); return ResponseEntity.status(HttpStatus.CREATED).body(response); } - @PutMapping("/courses/reviews/{rid}/like") + @PutMapping("/reviews/{rid}/like") public ResponseEntity likeReview(@PathVariable("rid") Long reviewId, @Login AuthInfo authInfo) { ReviewLikeResponse response= reviewService.likeReview(reviewId, authInfo); return ResponseEntity.ok(response); } - @GetMapping("/courses/reviews/me") - public ResponseEntity> findMyPosts(@Login AuthInfo authInfo) { + @GetMapping("/reviews/me") + public ResponseEntity> findMyReviews(@Login AuthInfo authInfo) { List reviewResponses = reviewService.findMyReviews(authInfo); return ResponseEntity.ok(reviewResponses); } - @PutMapping("/courses/reviews/{rid}") + @PutMapping("/reviews/{rid}") public ResponseEntity updateReview(@PathVariable("rid") Long reviewId, @Valid @RequestBody ReviewUpdateRequest reviewUpdateRequest, @Login AuthInfo authInfo) { @@ -60,12 +57,11 @@ public ResponseEntity updateReview(@PathVariable("rid") Lo return ResponseEntity.ok(response); } - @DeleteMapping("/courses/reviews/{rid}") + @DeleteMapping("/reviews/{rid}") public ResponseEntity deleteReview(@PathVariable("rid") Long reviewId, @Login AuthInfo authInfo) { ReviewDeleteResponse response = reviewService.deleteReview(reviewId, authInfo); return ResponseEntity.ok(response); } - } diff --git a/src/main/java/org/wooriverygood/api/review/service/ReviewService.java b/src/main/java/org/wooriverygood/api/review/application/ReviewService.java similarity index 90% rename from src/main/java/org/wooriverygood/api/review/service/ReviewService.java rename to src/main/java/org/wooriverygood/api/review/application/ReviewService.java index 0156687..86329fb 100644 --- a/src/main/java/org/wooriverygood/api/review/service/ReviewService.java +++ b/src/main/java/org/wooriverygood/api/review/application/ReviewService.java @@ -1,10 +1,10 @@ -package org.wooriverygood.api.review.service; +package org.wooriverygood.api.review.application; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.wooriverygood.api.advice.exception.CourseNotFoundException; -import org.wooriverygood.api.advice.exception.ReviewNotFoundException; +import org.wooriverygood.api.review.exception.ReviewNotFoundException; import org.wooriverygood.api.course.domain.Courses; import org.wooriverygood.api.course.repository.CourseRepository; import org.wooriverygood.api.review.domain.Review; @@ -14,8 +14,6 @@ import org.wooriverygood.api.review.repository.ReviewRepository; import org.wooriverygood.api.global.auth.AuthInfo; -import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; import java.util.List; import java.util.Optional; @@ -124,7 +122,7 @@ private ReviewLikeResponse createReviewLikeResponse(Review review, boolean liked public ReviewUpdateResponse updateReview(Long reviewId, ReviewUpdateRequest reviewUpdateRequest, AuthInfo authInfo) { Review review = reviewRepository.findById(reviewId) .orElseThrow(ReviewNotFoundException::new); -// review.validateAuthor(authInfo.getUsername()); + review.validateAuthor(authInfo.getUsername()); review.updateReview(reviewUpdateRequest.getReview_title(), reviewUpdateRequest.getInstructor_name(), reviewUpdateRequest.getTaken_semyr(), reviewUpdateRequest.getReview_content(), reviewUpdateRequest.getGrade(), authInfo.getUsername()); @@ -148,13 +146,4 @@ public ReviewDeleteResponse deleteReview(Long reviewId, AuthInfo authInfo) { .build(); } - public boolean canAccessReviews(AuthInfo authInfo) { - Optional review = reviewRepository.findTopByAuthorEmailOrderByCreatedAtDesc(authInfo.getUsername()); - if (review.isEmpty()) { - return false; - } - LocalDateTime now = LocalDateTime.now(); - long distance = ChronoUnit.MONTHS.between(review.get().getCreatedAt(), now); - return distance <= 6; - } } diff --git a/src/main/java/org/wooriverygood/api/review/application/ReviewValidateAccessService.java b/src/main/java/org/wooriverygood/api/review/application/ReviewValidateAccessService.java new file mode 100644 index 0000000..d6b2002 --- /dev/null +++ b/src/main/java/org/wooriverygood/api/review/application/ReviewValidateAccessService.java @@ -0,0 +1,31 @@ +package org.wooriverygood.api.review.application; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.wooriverygood.api.review.exception.ReviewAccessDeniedException; +import org.wooriverygood.api.global.auth.AuthInfo; +import org.wooriverygood.api.review.domain.Review; +import org.wooriverygood.api.review.repository.ReviewRepository; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class ReviewValidateAccessService { + + private final ReviewRepository reviewRepository; + + public void validateReviewAccess(AuthInfo authInfo) { + Review review = reviewRepository.findTopByAuthorEmailOrderByCreatedAtDesc(authInfo.getUsername()) + .orElseThrow(ReviewAccessDeniedException::new); + + LocalDateTime now = LocalDateTime.now(); + long distance = ChronoUnit.MONTHS.between(review.getCreatedAt(), now); + if (distance > 6) + throw new ReviewAccessDeniedException(); + } + +} diff --git a/src/main/java/org/wooriverygood/api/review/domain/Review.java b/src/main/java/org/wooriverygood/api/review/domain/Review.java index ee41882..f920904 100644 --- a/src/main/java/org/wooriverygood/api/review/domain/Review.java +++ b/src/main/java/org/wooriverygood/api/review/domain/Review.java @@ -7,7 +7,7 @@ import org.hibernate.annotations.ColumnDefault; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; -import org.wooriverygood.api.advice.exception.AuthorizationException; +import org.wooriverygood.api.global.error.exception.AuthorizationException; import org.wooriverygood.api.course.domain.Courses; import java.time.LocalDateTime; diff --git a/src/main/java/org/wooriverygood/api/advice/exception/ReviewAccessDeniedException.java b/src/main/java/org/wooriverygood/api/review/exception/ReviewAccessDeniedException.java similarity index 87% rename from src/main/java/org/wooriverygood/api/advice/exception/ReviewAccessDeniedException.java rename to src/main/java/org/wooriverygood/api/review/exception/ReviewAccessDeniedException.java index a146cc5..288c210 100644 --- a/src/main/java/org/wooriverygood/api/advice/exception/ReviewAccessDeniedException.java +++ b/src/main/java/org/wooriverygood/api/review/exception/ReviewAccessDeniedException.java @@ -1,4 +1,4 @@ -package org.wooriverygood.api.advice.exception; +package org.wooriverygood.api.review.exception; import org.wooriverygood.api.global.error.exception.BadRequestException; diff --git a/src/main/java/org/wooriverygood/api/advice/exception/ReviewNotFoundException.java b/src/main/java/org/wooriverygood/api/review/exception/ReviewNotFoundException.java similarity index 85% rename from src/main/java/org/wooriverygood/api/advice/exception/ReviewNotFoundException.java rename to src/main/java/org/wooriverygood/api/review/exception/ReviewNotFoundException.java index 15d0d19..2ae8736 100644 --- a/src/main/java/org/wooriverygood/api/advice/exception/ReviewNotFoundException.java +++ b/src/main/java/org/wooriverygood/api/review/exception/ReviewNotFoundException.java @@ -1,4 +1,4 @@ -package org.wooriverygood.api.advice.exception; +package org.wooriverygood.api.review.exception; import org.wooriverygood.api.global.error.exception.NotFoundException; diff --git a/src/test/java/org/wooriverygood/api/comment/controller/CommentApiTest.java b/src/test/java/org/wooriverygood/api/comment/controller/CommentApiTest.java index bcf3e1d..93c89ee 100644 --- a/src/test/java/org/wooriverygood/api/comment/controller/CommentApiTest.java +++ b/src/test/java/org/wooriverygood/api/comment/controller/CommentApiTest.java @@ -6,7 +6,7 @@ import org.mockito.Mockito; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; -import org.wooriverygood.api.advice.exception.AuthorizationException; +import org.wooriverygood.api.global.error.exception.AuthorizationException; import org.wooriverygood.api.advice.exception.ReplyDepthException; import org.wooriverygood.api.comment.dto.*; import org.wooriverygood.api.post.domain.Post; diff --git a/src/test/java/org/wooriverygood/api/comment/service/CommentServiceTest.java b/src/test/java/org/wooriverygood/api/comment/service/CommentServiceTest.java index 2f18873..cbe96d4 100644 --- a/src/test/java/org/wooriverygood/api/comment/service/CommentServiceTest.java +++ b/src/test/java/org/wooriverygood/api/comment/service/CommentServiceTest.java @@ -9,7 +9,7 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; -import org.wooriverygood.api.advice.exception.AuthorizationException; +import org.wooriverygood.api.global.error.exception.AuthorizationException; import org.wooriverygood.api.advice.exception.ReplyDepthException; import org.wooriverygood.api.comment.domain.Comment; import org.wooriverygood.api.comment.domain.CommentLike; diff --git a/src/test/java/org/wooriverygood/api/post/api/PostApiTest.java b/src/test/java/org/wooriverygood/api/post/api/PostApiTest.java index 96fe8ac..89f5f84 100644 --- a/src/test/java/org/wooriverygood/api/post/api/PostApiTest.java +++ b/src/test/java/org/wooriverygood/api/post/api/PostApiTest.java @@ -6,7 +6,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; -import org.wooriverygood.api.advice.exception.AuthorizationException; +import org.wooriverygood.api.global.error.exception.AuthorizationException; import org.wooriverygood.api.post.exception.PostNotFoundException; import org.wooriverygood.api.post.dto.*; import org.wooriverygood.api.global.auth.AuthInfo; @@ -50,8 +50,6 @@ void findPostsByCategory() { .postId(i) .postTitle("title_" + i) .postCategory("자유") - .postContent("content_" + i) - .postAuthor("author") .postComments((int) (Math.random() * 100)) .postLikes((int) (Math.random() * 100)) .postTime(LocalDateTime.now()) @@ -81,7 +79,17 @@ void findPostsByCategory() { @DisplayName("특정 게시글 조회 요청을 받으면 게시글을 반환한다.") void findPost() { when(postFindService.findPostById(anyLong(), any(AuthInfo.class))) - .thenReturn(ResponseFixture.postResponse(4L, testAuthInfo)); + .thenReturn(PostDetailResponse.builder() + .postId(1L) + .postTitle("title_1") + .postCategory("자유") + .postComments((int) (Math.random() * 100)) + .postLikes((int) (Math.random() * 100)) + .postTime(LocalDateTime.now()) + .liked(false) + .updated(false) + .reported(false) + .build()); restDocs .contentType(MediaType.APPLICATION_JSON_VALUE) diff --git a/src/test/java/org/wooriverygood/api/post/application/PostDeleteServiceTest.java b/src/test/java/org/wooriverygood/api/post/application/PostDeleteServiceTest.java index eb0bc9d..4d04c48 100644 --- a/src/test/java/org/wooriverygood/api/post/application/PostDeleteServiceTest.java +++ b/src/test/java/org/wooriverygood/api/post/application/PostDeleteServiceTest.java @@ -6,7 +6,7 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; -import org.wooriverygood.api.advice.exception.AuthorizationException; +import org.wooriverygood.api.global.error.exception.AuthorizationException; import org.wooriverygood.api.comment.repository.CommentRepository; import org.wooriverygood.api.post.domain.Post; import org.wooriverygood.api.post.domain.PostCategory; diff --git a/src/test/java/org/wooriverygood/api/post/application/PostFindServiceTest.java b/src/test/java/org/wooriverygood/api/post/application/PostFindServiceTest.java index 0c5c415..3d75646 100644 --- a/src/test/java/org/wooriverygood/api/post/application/PostFindServiceTest.java +++ b/src/test/java/org/wooriverygood/api/post/application/PostFindServiceTest.java @@ -6,13 +6,12 @@ import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.Mockito; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.wooriverygood.api.post.domain.Post; import org.wooriverygood.api.post.domain.PostCategory; -import org.wooriverygood.api.post.dto.PostResponse; +import org.wooriverygood.api.post.dto.PostDetailResponse; import org.wooriverygood.api.post.dto.PostsResponse; import org.wooriverygood.api.post.exception.PostNotFoundException; import org.wooriverygood.api.post.repository.PostLikeRepository; @@ -26,6 +25,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; class PostFindServiceTest extends MockTest { @@ -83,7 +83,7 @@ void findPosts_login() { int end = Math.min(start + pageable.getPageSize(), this.posts.size()); List posts = this.posts.subList(start, end); PageImpl page = new PageImpl<>(posts, pageable, this.posts.size()); - Mockito.when(postRepository.findAllByOrderByIdDesc(any(PageRequest.class))) + when(postRepository.findAllByOrderByIdDesc(any(PageRequest.class))) .thenReturn(page); PostsResponse response = postFindService.findPosts(authInfo, pageable, ""); @@ -101,7 +101,7 @@ void findPosts_category_free() { int end = Math.min(start + pageable.getPageSize(), this.freePosts.size()); List posts = this.freePosts.subList(start, end); PageImpl page = new PageImpl<>(posts, pageable, this.freePosts.size()); - Mockito.when(postRepository.findAllByCategoryOrderByIdDesc(any(PostCategory.class), any(PageRequest.class))) + when(postRepository.findAllByCategoryOrderByIdDesc(any(PostCategory.class), any(PageRequest.class))) .thenReturn(page); PostsResponse response = postFindService.findPosts(authInfo, pageable, "자유"); @@ -123,10 +123,10 @@ void findPostById() { .comments(new ArrayList<>()) .postLikes(new ArrayList<>()) .build(); - Mockito.when(postRepository.findById(any())) + when(postRepository.findById(any())) .thenReturn(Optional.ofNullable(singlePost)); - PostResponse response = postFindService.findPostById(6L, authInfo); + PostDetailResponse response = postFindService.findPostById(6L, authInfo); assertThat(response).isNotNull(); } @@ -134,7 +134,7 @@ void findPostById() { @Test @DisplayName("유효하지 않은 id를 이용하여 특정 게시글을 불러오면 에러를 반환한다.") void findPostById_exception_invalidId() { - Mockito.when(postRepository.findById(any())) + when(postRepository.findById(any())) .thenThrow(PostNotFoundException.class); Assertions.assertThatThrownBy(() -> postFindService.findPostById(6L, authInfo)) @@ -150,7 +150,7 @@ void findMyPosts() { int end = Math.min(start + pageable.getPageSize(), this.myPosts.size()); List posts = this.myPosts.subList(start, end); PageImpl page = new PageImpl<>(posts, pageable, this.myPosts.size()); - Mockito.when(postRepository.findByAuthorOrderByIdDesc(any(String.class), any(PageRequest.class))) + when(postRepository.findByAuthorOrderByIdDesc(any(String.class), any(PageRequest.class))) .thenReturn(page); PostsResponse response = postFindService.findMyPosts(authInfo, pageable); diff --git a/src/test/java/org/wooriverygood/api/post/application/PostUpdateServiceTest.java b/src/test/java/org/wooriverygood/api/post/application/PostUpdateServiceTest.java index feeec10..70c113d 100644 --- a/src/test/java/org/wooriverygood/api/post/application/PostUpdateServiceTest.java +++ b/src/test/java/org/wooriverygood/api/post/application/PostUpdateServiceTest.java @@ -5,7 +5,7 @@ import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.wooriverygood.api.advice.exception.AuthorizationException; +import org.wooriverygood.api.global.error.exception.AuthorizationException; import org.wooriverygood.api.post.domain.Post; import org.wooriverygood.api.post.domain.PostCategory; import org.wooriverygood.api.post.dto.PostUpdateRequest; diff --git a/src/test/java/org/wooriverygood/api/review/controller/ReviewApiTest.java b/src/test/java/org/wooriverygood/api/review/api/ReviewApiTest.java similarity index 85% rename from src/test/java/org/wooriverygood/api/review/controller/ReviewApiTest.java rename to src/test/java/org/wooriverygood/api/review/api/ReviewApiTest.java index 5084a94..d1ddbcd 100644 --- a/src/test/java/org/wooriverygood/api/review/controller/ReviewApiTest.java +++ b/src/test/java/org/wooriverygood/api/review/api/ReviewApiTest.java @@ -1,14 +1,13 @@ -package org.wooriverygood.api.review.controller; +package org.wooriverygood.api.review.api; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; -import org.wooriverygood.api.advice.exception.AuthorizationException; +import org.wooriverygood.api.global.error.exception.AuthorizationException; import org.wooriverygood.api.advice.exception.CourseNotFoundException; -import org.wooriverygood.api.advice.exception.ReviewAccessDeniedException; +import org.wooriverygood.api.review.exception.ReviewAccessDeniedException; import org.wooriverygood.api.course.domain.Courses; import org.wooriverygood.api.review.dto.*; import org.wooriverygood.api.global.auth.AuthInfo; @@ -19,12 +18,15 @@ import java.util.List; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.*; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; public class ReviewApiTest extends ApiTest { - List responses = new ArrayList<>(); - Courses course= Courses.builder() + private List responses = new ArrayList<>(); + + private Courses course = Courses.builder() .id(1L) .course_name("Gaoshu") .course_category("Zhuanye") @@ -33,7 +35,7 @@ public class ReviewApiTest extends ApiTest { .kaikeYuanxi("Xinke") .build(); - AuthInfo authInfo = AuthInfo.builder() + private AuthInfo authInfo = AuthInfo.builder() .sub("22222-34534-123") .username("22222-34534-123") .build(); @@ -59,9 +61,10 @@ void setUp() { @Test @DisplayName("최근 작성한 리뷰가 6개월 미만이라면, 특정 강의의 리뷰 조회 요청을 받으면 리뷰들을 반환한다.") void findAllReviewsByCourseId_success() { - Mockito.when(reviewService.canAccessReviews(any(AuthInfo.class))) - .thenReturn(true); - Mockito.when(reviewService.findAllReviewsByCourseId(any(), any(AuthInfo.class))) + doNothing() + .when(reviewValidateAccessService) + .validateReviewAccess(any(AuthInfo.class)); + when(reviewService.findAllReviewsByCourseId(anyLong(), any(AuthInfo.class))) .thenReturn(responses); restDocs @@ -76,8 +79,9 @@ void findAllReviewsByCourseId_success() { @Test @DisplayName("최근 작성한 리뷰가 6개월 이상이거나 없다면, 리뷰 요청에 대한 BadRequest 예외를 반환한다.") void findAllReviewsByCourseId_exception_accessDenied() { - Mockito.when(reviewService.canAccessReviews(any(AuthInfo.class))) - .thenThrow(new ReviewAccessDeniedException()); + doThrow(new ReviewAccessDeniedException()) + .when(reviewValidateAccessService) + .validateReviewAccess(any(AuthInfo.class)); restDocs .contentType(MediaType.APPLICATION_JSON_VALUE) @@ -91,9 +95,10 @@ void findAllReviewsByCourseId_exception_accessDenied() { @Test @DisplayName("유효하지 않은 수업의 리뷰를 조회하면 404를 반환한다.") void findReviews_exception_invalidId() { - Mockito.when(reviewService.canAccessReviews(any(AuthInfo.class))) - .thenReturn(true); - Mockito.when(reviewService.findAllReviewsByCourseId(any(Long.class), any(AuthInfo.class))) + doNothing() + .when(reviewValidateAccessService) + .validateReviewAccess(any(AuthInfo.class)); + when(reviewService.findAllReviewsByCourseId(any(Long.class), any(AuthInfo.class))) .thenThrow(new CourseNotFoundException()); restDocs @@ -127,7 +132,7 @@ void addReview() { .grade("100") .build(); - Mockito.when(reviewService.addReview(any(AuthInfo.class), any(Long.class), any(NewReviewRequest.class))) + when(reviewService.addReview(any(AuthInfo.class), any(Long.class), any(NewReviewRequest.class))) .thenReturn(response); restDocs @@ -144,7 +149,7 @@ void addReview() { @Test @DisplayName("특정 리뷰의 좋아요를 1 올리거나 내린다.") void likeReview() { - Mockito.when(reviewService.likeReview(any(Long.class), any(AuthInfo.class))) + when(reviewService.likeReview(any(Long.class), any(AuthInfo.class))) .thenReturn(ReviewLikeResponse.builder() .like_count(5) .liked(false) @@ -179,7 +184,7 @@ void findMyReviews() { .build()); } - Mockito.when(reviewService.findMyReviews(any(AuthInfo.class))) + when(reviewService.findMyReviews(any(AuthInfo.class))) .thenReturn(responses); restDocs @@ -203,7 +208,7 @@ void updateReview() { .grade("100") .build(); - Mockito.when(reviewService.updateReview(any(Long.class), any(ReviewUpdateRequest.class), any(AuthInfo.class))) + when(reviewService.updateReview(any(Long.class), any(ReviewUpdateRequest.class), any(AuthInfo.class))) .thenReturn(ReviewUpdateResponse.builder() .review_id((long)1) .build()); @@ -226,7 +231,7 @@ void updateReview_noAuth() { .review_content("new content") .build(); - Mockito.when(reviewService.updateReview(any(Long.class), any(ReviewUpdateRequest.class), any(AuthInfo.class))) + when(reviewService.updateReview(any(Long.class), any(ReviewUpdateRequest.class), any(AuthInfo.class))) .thenThrow(new AuthorizationException()); restDocs @@ -243,7 +248,7 @@ void updateReview_noAuth() { @Test @DisplayName("권한이 있는 리뷰를 삭제한다.") void deleteReview() { - Mockito.when(reviewService.deleteReview(any(Long.class), any(AuthInfo.class))) + when(reviewService.deleteReview(any(Long.class), any(AuthInfo.class))) .thenReturn(ReviewDeleteResponse.builder() .review_id(8L) .build()); @@ -260,7 +265,7 @@ void deleteReview() { @Test @DisplayName("권한이 없는 리뷰를 삭제하면 403을 반환한다.") void deleteReview_noAuth() { - Mockito.when(reviewService.deleteReview(any(Long.class), any(AuthInfo.class))) + when(reviewService.deleteReview(any(Long.class), any(AuthInfo.class))) .thenThrow(new AuthorizationException()); restDocs @@ -272,6 +277,4 @@ void deleteReview_noAuth() { .statusCode(HttpStatus.FORBIDDEN.value()); } - - } diff --git a/src/test/java/org/wooriverygood/api/review/service/ReviewServiceTest.java b/src/test/java/org/wooriverygood/api/review/application/ReviewServiceTest.java similarity index 87% rename from src/test/java/org/wooriverygood/api/review/service/ReviewServiceTest.java rename to src/test/java/org/wooriverygood/api/review/application/ReviewServiceTest.java index a00e419..c21a54b 100644 --- a/src/test/java/org/wooriverygood/api/review/service/ReviewServiceTest.java +++ b/src/test/java/org/wooriverygood/api/review/application/ReviewServiceTest.java @@ -1,4 +1,4 @@ -package org.wooriverygood.api.review.service; +package org.wooriverygood.api.review.application; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -9,7 +9,7 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; -import org.wooriverygood.api.advice.exception.AuthorizationException; +import org.wooriverygood.api.global.error.exception.AuthorizationException; import org.wooriverygood.api.advice.exception.CourseNotFoundException; import org.wooriverygood.api.course.domain.Courses; import org.wooriverygood.api.course.repository.CourseRepository; @@ -258,31 +258,4 @@ void deleteReview_noAuth() { .isInstanceOf(AuthorizationException.class); } - @Test - @DisplayName("리뷰를 작성하지 않았다면, false를 반환한다.") - void canAccessReviews_false_noReview() { - Mockito.when(reviewRepository.findTopByAuthorEmailOrderByCreatedAtDesc(any(String.class))) - .thenReturn(Optional.empty()); - - Assertions.assertThat(reviewService.canAccessReviews(authInfo)).isEqualTo(false); - } - - @Test - @DisplayName("마지막으로 작성한 리뷰가 현재 기준으로 6개월보다 멀다면, false를 반환한다.") - void canAccessReviews_false_sixMonths() { - Mockito.when(reviewRepository.findTopByAuthorEmailOrderByCreatedAtDesc(any(String.class))) - .thenReturn(Optional.ofNullable(singleReview)); - - Assertions.assertThat(reviewService.canAccessReviews(authInfo)).isEqualTo(false); - } - - @Test - @DisplayName("마지막으로 작성한 리뷰가 현재 기준으로 6개월보다 가깝다면, true를 반환한다.") - void canAccessReviews_true() { - Mockito.when(reviewRepository.findTopByAuthorEmailOrderByCreatedAtDesc(any(String.class))) - .thenReturn(Optional.ofNullable(noAuthReview)); - - Assertions.assertThat(reviewService.canAccessReviews(authInfo)).isEqualTo(true); - } - } diff --git a/src/test/java/org/wooriverygood/api/review/application/ReviewValidateAccessServiceTest.java b/src/test/java/org/wooriverygood/api/review/application/ReviewValidateAccessServiceTest.java new file mode 100644 index 0000000..99375b1 --- /dev/null +++ b/src/test/java/org/wooriverygood/api/review/application/ReviewValidateAccessServiceTest.java @@ -0,0 +1,71 @@ +package org.wooriverygood.api.review.application; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.wooriverygood.api.review.exception.ReviewAccessDeniedException; +import org.wooriverygood.api.global.auth.AuthInfo; +import org.wooriverygood.api.review.domain.Review; +import org.wooriverygood.api.review.repository.ReviewRepository; +import org.wooriverygood.api.util.MockTest; + +import java.time.LocalDateTime; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +class ReviewValidateAccessServiceTest extends MockTest { + + @InjectMocks + private ReviewValidateAccessService reviewValidateAccessService; + + @Mock + private ReviewRepository reviewRepository; + + private AuthInfo authInfo = AuthInfo.builder() + .sub("22222-34534-123") + .username("22222-34534-123") + .build(); + + + @Test + @DisplayName("마지막으로 작성한 리뷰가 현재 기준으로 6개월보다 가깝다면, 예외를 발생시키지 않는다.") + void canAccessReviews_true() { + Review review = Review.builder() + .createdAt(LocalDateTime.now().minusMonths(6)) + .build(); + + when(reviewRepository.findTopByAuthorEmailOrderByCreatedAtDesc(any(String.class))) + .thenReturn(Optional.ofNullable(review)); + + reviewValidateAccessService.validateReviewAccess(authInfo); + } + + @Test + @DisplayName("리뷰를 작성하지 않았다면, 예외를 발생시킨다.") + void canAccessReviews_false_noReview() { + when(reviewRepository.findTopByAuthorEmailOrderByCreatedAtDesc(any(String.class))) + .thenReturn(Optional.empty()); + + assertThatThrownBy(() -> reviewValidateAccessService.validateReviewAccess(authInfo)) + .isInstanceOf(ReviewAccessDeniedException.class); + } + + @Test + @DisplayName("마지막으로 작성한 리뷰가 현재 기준으로 6개월보다 멀다면, 예외를 발생시킨다.") + void canAccessReviews_false_sixMonths() { + Review review = Review.builder() + .createdAt(LocalDateTime.of(2022, 6, 13, 12, 00)) + .build(); + + when(reviewRepository.findTopByAuthorEmailOrderByCreatedAtDesc(any(String.class))) + .thenReturn(Optional.ofNullable(review)); + + assertThatThrownBy(() -> reviewValidateAccessService.validateReviewAccess(authInfo)) + .isInstanceOf(ReviewAccessDeniedException.class); + } + +} \ No newline at end of file diff --git a/src/test/java/org/wooriverygood/api/util/ApiTest.java b/src/test/java/org/wooriverygood/api/util/ApiTest.java index d254ba4..bfc9b35 100644 --- a/src/test/java/org/wooriverygood/api/util/ApiTest.java +++ b/src/test/java/org/wooriverygood/api/util/ApiTest.java @@ -19,8 +19,9 @@ import org.wooriverygood.api.course.controller.CourseController; import org.wooriverygood.api.course.service.CourseService; import org.wooriverygood.api.post.api.PostApi; -import org.wooriverygood.api.review.controller.ReviewController; -import org.wooriverygood.api.review.service.ReviewService; +import org.wooriverygood.api.review.api.ReviewApi; +import org.wooriverygood.api.review.application.ReviewValidateAccessService; +import org.wooriverygood.api.review.application.ReviewService; import org.wooriverygood.api.global.auth.AuthInfo; import org.wooriverygood.api.global.auth.AuthenticationPrincipalArgumentResolver; @@ -35,7 +36,7 @@ CommentController.class, PostApi.class, CourseController.class, - ReviewController.class, + ReviewApi.class, ReportController.class }) @WithMockUser @@ -50,6 +51,9 @@ public class ApiTest { @MockBean protected ReviewService reviewService; + @MockBean + protected ReviewValidateAccessService reviewValidateAccessService; + @MockBean protected CommentService commentService; diff --git a/src/test/java/org/wooriverygood/api/util/ResponseFixture.java b/src/test/java/org/wooriverygood/api/util/ResponseFixture.java index 9471b33..81e73ee 100644 --- a/src/test/java/org/wooriverygood/api/util/ResponseFixture.java +++ b/src/test/java/org/wooriverygood/api/util/ResponseFixture.java @@ -24,8 +24,6 @@ public static PostResponse postResponse(long id, String category, AuthInfo authI .postId(id) .postTitle("title_" + id) .postCategory(category) - .postContent("content_" + id) - .postAuthor(authInfo.getUsername()) .postComments((int) (Math.random() * 100)) .postLikes((int) (Math.random() * 100)) .postTime(LocalDateTime.now()) @@ -48,8 +46,6 @@ public static PostResponse reportedPostResponse(long id, String category, AuthIn .postId(id) .postTitle(null) .postCategory(category) - .postContent(null) - .postAuthor(authInfo.getUsername()) .postComments((int) (Math.random() * 100)) .postLikes((int) (Math.random() * 100)) .postTime(LocalDateTime.now()) From 278798a02b2b0476260df548cb7a13c0cc6fb22c Mon Sep 17 00:00:00 2001 From: Park Geonho Date: Tue, 2 Apr 2024 01:15:02 +0800 Subject: [PATCH 09/21] =?UTF-8?q?[refactor]=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 추가 - 리뷰 관련 api 엔드포인트 변경 --- .idea/workspace.xml | 74 +++++++++----- src/docs/asciidoc/index.adoc | 2 - .../java/org/wooriverygood/api/SampleApi.java | 2 + .../domain/{Courses.java => Course.java} | 4 +- .../api/course/dto/CourseResponse.java | 4 +- .../course/repository/CourseRepository.java | 4 +- .../api/course/service/CourseService.java | 14 +-- .../api/review/api/ReviewApi.java | 22 +++-- .../review/application/ReviewFindService.java | 48 +++++++++ .../api/review/application/ReviewService.java | 34 +++---- .../api/review/domain/Review.java | 6 +- .../api/review/dto/ReviewResponse.java | 55 ++++++----- .../api/review/dto/ReviewsResponse.java | 17 ++++ .../api/course/service/CourseServiceTest.java | 18 ++-- .../api/review/api/ReviewApiTest.java | 67 +++++-------- .../application/ReviewFindServiceTest.java | 98 +++++++++++++++++++ .../review/application/ReviewServiceTest.java | 40 +------- .../org/wooriverygood/api/util/ApiTest.java | 4 + 18 files changed, 325 insertions(+), 188 deletions(-) rename src/main/java/org/wooriverygood/api/course/domain/{Courses.java => Course.java} (87%) create mode 100644 src/main/java/org/wooriverygood/api/review/application/ReviewFindService.java create mode 100644 src/main/java/org/wooriverygood/api/review/dto/ReviewsResponse.java create mode 100644 src/test/java/org/wooriverygood/api/review/application/ReviewFindServiceTest.java diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 8ab989a..0a390f5 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -4,7 +4,10 @@ - + + + + @@ -106,6 +109,7 @@ "Gradle.CommentServiceTest.updateComment_exception_noAuth.executor": "Run", "Gradle.CourseServiceTest.executor": "Debug", "Gradle.PostApiTest.executor": "Run", + "Gradle.PostApiTest.findPosts.executor": "Run", "Gradle.PostControllerTest.addPost.executor": "Run", "Gradle.PostControllerTest.addPost_exception_noTitle.executor": "Run", "Gradle.PostControllerTest.deletePost.executor": "Run", @@ -122,6 +126,7 @@ "Gradle.PostDeleteServiceTest.deletePost_exception_noAuth.executor": "Run", "Gradle.PostDeleteServiceTest.executor": "Run", "Gradle.PostFindMockTest.executor": "Run", + "Gradle.PostFindServiceTest.executor": "Run", "Gradle.PostLikeToggleServiceTest.executor": "Run", "Gradle.PostServiceTest.addPost_exception_invalid_category.executor": "Run", "Gradle.PostServiceTest.deletePost.executor": "Run", @@ -141,11 +146,14 @@ "Gradle.ReportServiceTest.reportPost.executor": "Run", "Gradle.ReportServiceTest.reportPost_exception_duplicated.executor": "Run", "Gradle.ReportServiceTest.reportPost_exception_invalidId.executor": "Run", + "Gradle.ReviewAccessCheckServiceTest.executor": "Run", + "Gradle.ReviewApiTest.executor": "Coverage", "Gradle.ReviewControllerTest.findAllReviewsByCourseId_exception_accessDenied.executor": "Run", "Gradle.ReviewControllerTest.findAllReviewsByCourseId_success.executor": "Run", "Gradle.ReviewServiceTest.canAccessReviews_false_noReview.executor": "Run", "Gradle.ReviewServiceTest.canAccessReviews_false_sixMonths.executor": "Run", "Gradle.ReviewServiceTest.canAccessReviews_true.executor": "Run", + "Gradle.ReviewValidateAccessServiceTest.executor": "Run", "Notification.DisplayName-DoNotAsk-Lombok plugin": "Lombok integration problem", "Notification.DoNotAsk-Lombok plugin": "true", "RequestMappingsPanelOrder0": "0", @@ -195,13 +203,14 @@ + - + - + - + - + - + - - - - + + + + + + + + - - - - @@ -375,7 +384,7 @@ - + - @@ -739,8 +764,6 @@ - - @@ -764,7 +787,9 @@ - @@ -778,6 +803,7 @@ + \ No newline at end of file diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index bf78468..769b606 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -29,8 +29,6 @@ operation::reviews/find/success[snippets='http-request,http-response'] ==== 실패 ===== 최근 작성한 리뷰가 6개월 이상이거나, 없을경우 operation::reviews/find/fail/denied[snippets='http-request,http-response'] -===== 수업이 존재하지 않을 경우 -operation::reviews/find/fail/noCourse[snippets='http-request,http-response'] === 특정 강의의 리뷰 작성하기 (POST /courses/{id}/reviews) ==== 성공 diff --git a/src/main/java/org/wooriverygood/api/SampleApi.java b/src/main/java/org/wooriverygood/api/SampleApi.java index 6996e38..ab55589 100644 --- a/src/main/java/org/wooriverygood/api/SampleApi.java +++ b/src/main/java/org/wooriverygood/api/SampleApi.java @@ -1,11 +1,13 @@ package org.wooriverygood.api; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class SampleApi { + @GetMapping("/") public ResponseEntity sample() { return ResponseEntity.ok("api is online"); } diff --git a/src/main/java/org/wooriverygood/api/course/domain/Courses.java b/src/main/java/org/wooriverygood/api/course/domain/Course.java similarity index 87% rename from src/main/java/org/wooriverygood/api/course/domain/Courses.java rename to src/main/java/org/wooriverygood/api/course/domain/Course.java index 77d1f31..abaf526 100644 --- a/src/main/java/org/wooriverygood/api/course/domain/Courses.java +++ b/src/main/java/org/wooriverygood/api/course/domain/Course.java @@ -10,7 +10,7 @@ @Getter @NoArgsConstructor @Table(name = "courses") -public class Courses { +public class Course { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "course_id") @@ -36,7 +36,7 @@ public class Courses { private int reviewCount; @Builder - public Courses(Long id, String course_name, String course_category, double course_credit, int isYouguan, String kaikeYuanxi, int reviewCount) { + public Course(Long id, String course_name, String course_category, double course_credit, int isYouguan, String kaikeYuanxi, int reviewCount) { this.id = id; this.course_name = course_name; this.course_category = course_category; diff --git a/src/main/java/org/wooriverygood/api/course/dto/CourseResponse.java b/src/main/java/org/wooriverygood/api/course/dto/CourseResponse.java index 1281328..859e97e 100644 --- a/src/main/java/org/wooriverygood/api/course/dto/CourseResponse.java +++ b/src/main/java/org/wooriverygood/api/course/dto/CourseResponse.java @@ -2,7 +2,7 @@ import lombok.Builder; import lombok.Getter; -import org.wooriverygood.api.course.domain.Courses; +import org.wooriverygood.api.course.domain.Course; @Getter public class CourseResponse { @@ -25,7 +25,7 @@ public CourseResponse(Long course_id, String course_category, double course_cred this.reviewCount = reviewCount; } - public static CourseResponse from(Courses course) { + public static CourseResponse from(Course course) { return CourseResponse.builder() .course_id(course.getId()) .course_category(course.getCourse_category()) diff --git a/src/main/java/org/wooriverygood/api/course/repository/CourseRepository.java b/src/main/java/org/wooriverygood/api/course/repository/CourseRepository.java index 29d009e..ac666f1 100644 --- a/src/main/java/org/wooriverygood/api/course/repository/CourseRepository.java +++ b/src/main/java/org/wooriverygood/api/course/repository/CourseRepository.java @@ -1,7 +1,7 @@ package org.wooriverygood.api.course.repository; import org.springframework.data.jpa.repository.JpaRepository; -import org.wooriverygood.api.course.domain.Courses; +import org.wooriverygood.api.course.domain.Course; -public interface CourseRepository extends JpaRepository { +public interface CourseRepository extends JpaRepository { } diff --git a/src/main/java/org/wooriverygood/api/course/service/CourseService.java b/src/main/java/org/wooriverygood/api/course/service/CourseService.java index 74341ec..7753192 100644 --- a/src/main/java/org/wooriverygood/api/course/service/CourseService.java +++ b/src/main/java/org/wooriverygood/api/course/service/CourseService.java @@ -4,7 +4,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.wooriverygood.api.advice.exception.CourseNotFoundException; -import org.wooriverygood.api.course.domain.Courses; +import org.wooriverygood.api.course.domain.Course; import org.wooriverygood.api.course.dto.CourseNameResponse; import org.wooriverygood.api.course.dto.CourseResponse; import org.wooriverygood.api.course.dto.NewCourseRequest; @@ -25,13 +25,13 @@ public List findAll() { @Transactional public NewCourseResponse addCourse(NewCourseRequest newCourseRequest) { - Courses course = createCourse(newCourseRequest); - Courses saved = courseRepository.save(course); + Course course = createCourse(newCourseRequest); + Course saved = courseRepository.save(course); return createResponse(saved); } public CourseNameResponse getCourseName(Long courseId) { - Courses courses = courseRepository.findById(courseId) + Course courses = courseRepository.findById(courseId) .orElseThrow(CourseNotFoundException::new); return CourseNameResponse.builder() @@ -40,8 +40,8 @@ public CourseNameResponse getCourseName(Long courseId) { } - private Courses createCourse(NewCourseRequest newCourseRequest) { - return Courses.builder() + private Course createCourse(NewCourseRequest newCourseRequest) { + return Course.builder() .course_name(newCourseRequest.getCourse_name()) .course_category(newCourseRequest.getCourse_category()) .course_credit(newCourseRequest.getCourse_credit()) @@ -50,7 +50,7 @@ private Courses createCourse(NewCourseRequest newCourseRequest) { .build(); } - private NewCourseResponse createResponse(Courses course) { + private NewCourseResponse createResponse(Course course) { return NewCourseResponse.builder() .course_id(course.getId()) .course_name(course.getCourse_name()) diff --git a/src/main/java/org/wooriverygood/api/review/api/ReviewApi.java b/src/main/java/org/wooriverygood/api/review/api/ReviewApi.java index 7587094..48b1a69 100644 --- a/src/main/java/org/wooriverygood/api/review/api/ReviewApi.java +++ b/src/main/java/org/wooriverygood/api/review/api/ReviewApi.java @@ -5,6 +5,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import org.wooriverygood.api.review.application.ReviewFindService; import org.wooriverygood.api.review.application.ReviewValidateAccessService; import org.wooriverygood.api.review.dto.*; import org.wooriverygood.api.review.application.ReviewService; @@ -14,24 +15,25 @@ import java.util.List; @RestController -@RequestMapping("/courses") @RequiredArgsConstructor public class ReviewApi { private final ReviewService reviewService; + private final ReviewFindService reviewFindService; + private final ReviewValidateAccessService reviewValidateAccessService; - @GetMapping("/{id}/reviews") - public ResponseEntity> findAllReviewsByCourseId(@PathVariable("id") Long courseId, - @Login AuthInfo authInfo) { + @GetMapping("/courses/{id}/reviews") + public ResponseEntity findAllReviewsByCourseId(@PathVariable("id") Long courseId, + @Login AuthInfo authInfo) { reviewValidateAccessService.validateReviewAccess(authInfo); - List reviews = reviewService.findAllReviewsByCourseId(courseId, authInfo); - return ResponseEntity.ok(reviews); + ReviewsResponse response = reviewFindService.findAllReviewsByCourseId(courseId, authInfo); + return ResponseEntity.ok(response); } - @PostMapping("/{id}/reviews") + @PostMapping("/courses/{id}/reviews") public ResponseEntity addReview(@PathVariable("id") Long courseId, @Login AuthInfo authInfo, @Valid @RequestBody NewReviewRequest newReviewRequest) { NewReviewResponse response = reviewService.addReview(authInfo, courseId, newReviewRequest); return ResponseEntity.status(HttpStatus.CREATED).body(response); @@ -44,9 +46,9 @@ public ResponseEntity likeReview(@PathVariable("rid") Long r } @GetMapping("/reviews/me") - public ResponseEntity> findMyReviews(@Login AuthInfo authInfo) { - List reviewResponses = reviewService.findMyReviews(authInfo); - return ResponseEntity.ok(reviewResponses); + public ResponseEntity findMyReviews(@Login AuthInfo authInfo) { + ReviewsResponse response = reviewFindService.findMyReviews(authInfo); + return ResponseEntity.ok(response); } @PutMapping("/reviews/{rid}") diff --git a/src/main/java/org/wooriverygood/api/review/application/ReviewFindService.java b/src/main/java/org/wooriverygood/api/review/application/ReviewFindService.java new file mode 100644 index 0000000..d6cd6cb --- /dev/null +++ b/src/main/java/org/wooriverygood/api/review/application/ReviewFindService.java @@ -0,0 +1,48 @@ +package org.wooriverygood.api.review.application; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.wooriverygood.api.global.auth.AuthInfo; +import org.wooriverygood.api.review.domain.Review; +import org.wooriverygood.api.review.dto.ReviewResponse; +import org.wooriverygood.api.review.dto.ReviewsResponse; +import org.wooriverygood.api.review.repository.ReviewLikeRepository; +import org.wooriverygood.api.review.repository.ReviewRepository; + +import java.util.List; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class ReviewFindService { + + private final ReviewRepository reviewRepository; + + private final ReviewLikeRepository reviewLikeRepository; + + + public ReviewsResponse findAllReviewsByCourseId(Long courseId, AuthInfo authInfo) { + List reviews = reviewRepository.findAllByCourseId(courseId); + + return new ReviewsResponse(reviews.stream() + .map(review -> { + boolean liked = reviewLikeRepository.existsByReviewAndUsername(review, authInfo.getUsername()); + return ReviewResponse.of(review, + review.isSameAuthor(authInfo.getUsername()), + liked); + }) + .toList()); + } + + public ReviewsResponse findMyReviews(AuthInfo authInfo) { + List reviews= reviewRepository.findByAuthorEmail(authInfo.getUsername()); + return new ReviewsResponse(reviews.stream() + .map(review -> { + boolean liked = reviewLikeRepository.existsByReviewAndUsername(review, authInfo.getUsername()); + return ReviewResponse.of(review, true, liked); + }) + .toList()); + } + +} diff --git a/src/main/java/org/wooriverygood/api/review/application/ReviewService.java b/src/main/java/org/wooriverygood/api/review/application/ReviewService.java index 86329fb..a0749fb 100644 --- a/src/main/java/org/wooriverygood/api/review/application/ReviewService.java +++ b/src/main/java/org/wooriverygood/api/review/application/ReviewService.java @@ -5,7 +5,7 @@ import org.springframework.transaction.annotation.Transactional; import org.wooriverygood.api.advice.exception.CourseNotFoundException; import org.wooriverygood.api.review.exception.ReviewNotFoundException; -import org.wooriverygood.api.course.domain.Courses; +import org.wooriverygood.api.course.domain.Course; import org.wooriverygood.api.course.repository.CourseRepository; import org.wooriverygood.api.review.domain.Review; import org.wooriverygood.api.review.domain.ReviewLike; @@ -25,24 +25,24 @@ public class ReviewService { private final CourseRepository courseRepository; private final ReviewLikeRepository reviewLikeRepository; - public List findAllReviewsByCourseId(Long courseId, AuthInfo authInfo) { - Courses course = courseRepository.findById(courseId) - .orElseThrow(CourseNotFoundException::new); - List reviews = reviewRepository.findAllByCourseId(courseId); - - if(authInfo.getUsername() == null) { - return reviews.stream() - .map(review -> ReviewResponse.from(review, false, reviewLikeRepository.existsByReviewAndUsername(review, authInfo.getUsername()))) - .toList(); - } - return reviews.stream() - .map(review -> ReviewResponse.from(review, review.isSameAuthor(authInfo.getUsername()), reviewLikeRepository.existsByReviewAndUsername(review, authInfo.getUsername()))) - .toList(); - } +// public List findAllReviewsByCourseId(Long courseId, AuthInfo authInfo) { +// Course course = courseRepository.findById(courseId) +// .orElseThrow(CourseNotFoundException::new); +// List reviews = reviewRepository.findAllByCourseId(courseId); +// +// if(authInfo.getUsername() == null) { +// return reviews.stream() +// .map(review -> ReviewResponse.of(review, false, reviewLikeRepository.existsByReviewAndUsername(review, authInfo.getUsername()))) +// .toList(); +// } +// return reviews.stream() +// .map(review -> ReviewResponse.of(review, review.isSameAuthor(authInfo.getUsername()), reviewLikeRepository.existsByReviewAndUsername(review, authInfo.getUsername()))) +// .toList(); +// } @Transactional public NewReviewResponse addReview(AuthInfo authInfo, Long courseId, NewReviewRequest newReviewRequest) { - Courses course = courseRepository.findById(courseId) + Course course = courseRepository.findById(courseId) .orElseThrow(CourseNotFoundException::new); Review review = Review.builder() .reviewTitle(newReviewRequest.getReview_title()) @@ -93,7 +93,7 @@ private void deleteReviewLike(Review review, ReviewLike reviewLike) { public List findMyReviews(AuthInfo authInfo) { List reviews= reviewRepository.findByAuthorEmail(authInfo.getUsername()); - return reviews.stream().map(review -> ReviewResponse.from(review, true, reviewLikeRepository.existsByReviewAndUsername(review, authInfo.getUsername()))).toList(); + return reviews.stream().map(review -> ReviewResponse.of(review, true, reviewLikeRepository.existsByReviewAndUsername(review, authInfo.getUsername()))).toList(); } diff --git a/src/main/java/org/wooriverygood/api/review/domain/Review.java b/src/main/java/org/wooriverygood/api/review/domain/Review.java index f920904..b361cd8 100644 --- a/src/main/java/org/wooriverygood/api/review/domain/Review.java +++ b/src/main/java/org/wooriverygood/api/review/domain/Review.java @@ -8,7 +8,7 @@ import org.springframework.data.annotation.CreatedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; import org.wooriverygood.api.global.error.exception.AuthorizationException; -import org.wooriverygood.api.course.domain.Courses; +import org.wooriverygood.api.course.domain.Course; import java.time.LocalDateTime; import java.util.List; @@ -26,7 +26,7 @@ public class Review { @ManyToOne @JoinColumn(name = "course_id", referencedColumnName = "course_id") - private Courses course; + private Course course; @Column(name = "review_content", length = 1000) private String reviewContent; @@ -61,7 +61,7 @@ public class Review { private boolean updated; @Builder - public Review(Long id, Courses course, String reviewContent, String reviewTitle, String instructorName, String takenSemyr, String grade, String authorEmail, LocalDateTime createdAt, List reviewLikes, boolean updated) { + public Review(Long id, Course course, String reviewContent, String reviewTitle, String instructorName, String takenSemyr, String grade, String authorEmail, LocalDateTime createdAt, List reviewLikes, boolean updated) { this.id = id; this.course = course; this.reviewContent = reviewContent; diff --git a/src/main/java/org/wooriverygood/api/review/dto/ReviewResponse.java b/src/main/java/org/wooriverygood/api/review/dto/ReviewResponse.java index 88cd695..c7dfd6e 100644 --- a/src/main/java/org/wooriverygood/api/review/dto/ReviewResponse.java +++ b/src/main/java/org/wooriverygood/api/review/dto/ReviewResponse.java @@ -1,5 +1,7 @@ package org.wooriverygood.api.review.dto; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; import lombok.Builder; import lombok.Getter; import org.wooriverygood.api.review.domain.Review; @@ -7,47 +9,48 @@ import java.time.LocalDateTime; @Getter +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) public class ReviewResponse { - private final Long review_id; - private final Long course_id; - private final String review_content; - private final String review_title; - private final String instructor_name; - private final String taken_semyr; + private final Long reviewId; + private final Long courseId; + private final String reviewContent; + private final String reviewTitle; + private final String instructorName; + private final String takenSemyr; private final String grade; - private final int like_count; - private final LocalDateTime review_time; + private final int likeCount; + private final LocalDateTime reviewTime; private final boolean isMine; private final boolean liked; private final boolean updated; @Builder - public ReviewResponse(Long review_id, Long course_id, String review_content, String review_title, String instructor_name, String taken_semyr, String grade, int like_count, LocalDateTime review_time, boolean isMine, boolean liked, boolean updated) { - this.review_id = review_id; - this.course_id = course_id; - this.review_content = review_content; - this.review_title = review_title; - this.instructor_name = instructor_name; - this.taken_semyr = taken_semyr; + public ReviewResponse(Long reviewId, Long courseId, String reviewContent, String reviewTitle, String instructorName, String takenSemyr, String grade, int likeCount, LocalDateTime reviewTime, boolean isMine, boolean liked, boolean updated) { + this.reviewId = reviewId; + this.courseId = courseId; + this.reviewContent = reviewContent; + this.reviewTitle = reviewTitle; + this.instructorName = instructorName; + this.takenSemyr = takenSemyr; this.grade = grade; - this.like_count = like_count; - this.review_time = review_time; + this.likeCount = likeCount; + this.reviewTime = reviewTime; this.isMine = isMine; this.liked = liked; this.updated = updated; } - public static ReviewResponse from(Review review, boolean isMine, boolean liked) { + public static ReviewResponse of(Review review, boolean isMine, boolean liked) { return ReviewResponse.builder() - .review_id(review.getId()) - .course_id(review.getCourse().getId()) - .review_content(review.getReviewContent()) - .review_title(review.getReviewTitle()) - .instructor_name(review.getInstructorName()) - .taken_semyr(review.getTakenSemyr()) + .reviewId(review.getId()) + .courseId(review.getCourse().getId()) + .reviewContent(review.getReviewContent()) + .reviewTitle(review.getReviewTitle()) + .instructorName(review.getInstructorName()) + .takenSemyr(review.getTakenSemyr()) .grade(review.getGrade()) - .like_count(review.getLikeCount()) - .review_time(review.getCreatedAt()) + .likeCount(review.getLikeCount()) + .reviewTime(review.getCreatedAt()) .isMine(isMine) .liked(liked) .updated(review.isUpdated()) diff --git a/src/main/java/org/wooriverygood/api/review/dto/ReviewsResponse.java b/src/main/java/org/wooriverygood/api/review/dto/ReviewsResponse.java new file mode 100644 index 0000000..fa04cae --- /dev/null +++ b/src/main/java/org/wooriverygood/api/review/dto/ReviewsResponse.java @@ -0,0 +1,17 @@ +package org.wooriverygood.api.review.dto; + +import lombok.Getter; + +import java.util.List; + +@Getter +public class ReviewsResponse { + + private final List reviews; + + + public ReviewsResponse(List reviews) { + this.reviews = reviews; + } + +} diff --git a/src/test/java/org/wooriverygood/api/course/service/CourseServiceTest.java b/src/test/java/org/wooriverygood/api/course/service/CourseServiceTest.java index 6987151..b4fd260 100644 --- a/src/test/java/org/wooriverygood/api/course/service/CourseServiceTest.java +++ b/src/test/java/org/wooriverygood/api/course/service/CourseServiceTest.java @@ -9,23 +9,17 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.transaction.annotation.Transactional; -import org.wooriverygood.api.course.domain.Courses; +import org.wooriverygood.api.course.domain.Course; import org.wooriverygood.api.course.dto.CourseNameResponse; import org.wooriverygood.api.course.dto.CourseResponse; import org.wooriverygood.api.course.dto.NewCourseRequest; import org.wooriverygood.api.course.dto.NewCourseResponse; import org.wooriverygood.api.course.repository.CourseRepository; -import javax.swing.text.html.Option; import java.util.ArrayList; import java.util.List; import java.util.Optional; -import static org.mockito.Mockito.when; import static org.mockito.ArgumentMatchers.any; @ExtendWith(MockitoExtension.class) @@ -35,14 +29,14 @@ public class CourseServiceTest { @InjectMocks private CourseService courseService; - private List testCourses = new ArrayList<>(); + private List testCourses = new ArrayList<>(); private final int COURSES_COUNT = 10; @BeforeEach void setup() { for(int i = 1; i<=COURSES_COUNT; i++) { - testCourses.add(Courses.builder() + testCourses.add(Course.builder() .id((long)i) .course_name("Gaoshu" + i) .course_category("Zhuanye") @@ -54,7 +48,7 @@ void setup() { } } - Courses singleCourse = Courses.builder() + Course singleCourse = Course.builder() .id(3L) .course_name("Gaoshu Test") .course_category("Zhuanye") @@ -88,8 +82,8 @@ void addCourse() { .isYouguan(0) .build(); - Mockito.when(courseRepository.save(any(Courses.class))) - .thenReturn(Courses.builder() + Mockito.when(courseRepository.save(any(Course.class))) + .thenReturn(Course.builder() .course_name(newCourseRequest.getCourse_name()) .course_category(newCourseRequest.getCourse_category()) .course_credit(newCourseRequest.getCourse_credit()) diff --git a/src/test/java/org/wooriverygood/api/review/api/ReviewApiTest.java b/src/test/java/org/wooriverygood/api/review/api/ReviewApiTest.java index d1ddbcd..e56218e 100644 --- a/src/test/java/org/wooriverygood/api/review/api/ReviewApiTest.java +++ b/src/test/java/org/wooriverygood/api/review/api/ReviewApiTest.java @@ -8,7 +8,7 @@ import org.wooriverygood.api.global.error.exception.AuthorizationException; import org.wooriverygood.api.advice.exception.CourseNotFoundException; import org.wooriverygood.api.review.exception.ReviewAccessDeniedException; -import org.wooriverygood.api.course.domain.Courses; +import org.wooriverygood.api.course.domain.Course; import org.wooriverygood.api.review.dto.*; import org.wooriverygood.api.global.auth.AuthInfo; import org.wooriverygood.api.util.ApiTest; @@ -26,7 +26,7 @@ public class ReviewApiTest extends ApiTest { private List responses = new ArrayList<>(); - private Courses course = Courses.builder() + private Course course = Course.builder() .id(1L) .course_name("Gaoshu") .course_category("Zhuanye") @@ -44,14 +44,14 @@ public class ReviewApiTest extends ApiTest { void setUp() { for(int i = 1; i <= 2; i++) { responses.add(ReviewResponse.builder() - .review_id((long)i) - .course_id(1L) - .review_content("Test Review " + i) - .review_title("Title" + i) - .instructor_name("Jiaoshou") - .taken_semyr("22-23") + .reviewId((long)i) + .courseId(1L) + .reviewContent("Test Review " + i) + .reviewTitle("Title" + i) + .instructorName("Jiaoshou") + .takenSemyr("22-23") .grade("60") - .review_time(LocalDateTime.now()) + .reviewTime(LocalDateTime.now()) .isMine(false) .updated(false) .build()); @@ -64,8 +64,8 @@ void findAllReviewsByCourseId_success() { doNothing() .when(reviewValidateAccessService) .validateReviewAccess(any(AuthInfo.class)); - when(reviewService.findAllReviewsByCourseId(anyLong(), any(AuthInfo.class))) - .thenReturn(responses); + when(reviewFindService.findAllReviewsByCourseId(anyLong(), any(AuthInfo.class))) + .thenReturn(new ReviewsResponse(responses)); restDocs .contentType(MediaType.APPLICATION_JSON_VALUE) @@ -92,25 +92,6 @@ void findAllReviewsByCourseId_exception_accessDenied() { .statusCode(HttpStatus.BAD_REQUEST.value()); } - @Test - @DisplayName("유효하지 않은 수업의 리뷰를 조회하면 404를 반환한다.") - void findReviews_exception_invalidId() { - doNothing() - .when(reviewValidateAccessService) - .validateReviewAccess(any(AuthInfo.class)); - when(reviewService.findAllReviewsByCourseId(any(Long.class), any(AuthInfo.class))) - .thenThrow(new CourseNotFoundException()); - - restDocs - .contentType(MediaType.APPLICATION_JSON_VALUE) - .header("Authorization", "Bearer aws-cognito-access-token") - .when().get("/courses/1/reviews") - .then().log().all() - .assertThat() - .apply(document("reviews/find/fail/noCourse")) - .statusCode(HttpStatus.NOT_FOUND.value()); - } - @Test @DisplayName("특정 강의의 리뷰를 작성한다.") void addReview() { @@ -158,7 +139,7 @@ void likeReview() { restDocs .contentType(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer aws-cognito-access-token") - .when().put("/courses/reviews/1/like") + .when().put("/reviews/1/like") .then().log().all() .assertThat() .apply(document("reviews/like/success")) @@ -171,14 +152,14 @@ void findMyReviews() { List responses = new ArrayList<>(); for (int i = 1; i < 5; i++) { responses.add(ReviewResponse.builder() - .review_id((long)i) - .course_id(1L) - .review_content("Test Review " + i) - .review_title("Title" + i) - .instructor_name("Jiaoshou") - .taken_semyr("22-23") + .reviewId((long)i) + .courseId(1L) + .reviewContent("Test Review " + i) + .reviewTitle("Title" + i) + .instructorName("Jiaoshou") + .takenSemyr("22-23") .grade("60") - .review_time(LocalDateTime.now()) + .reviewTime(LocalDateTime.now()) .isMine(true) .updated(false) .build()); @@ -190,7 +171,7 @@ void findMyReviews() { restDocs .contentType(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer aws-cognito-access-token") - .when().get("/courses/reviews/me") + .when().get("/reviews/me") .then().log().all() .assertThat() .apply(document("reviews/find/me/success")) @@ -216,7 +197,7 @@ void updateReview() { .contentType(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer aws-cognito-access-token") .body(request) - .when().put("/courses/reviews/1") + .when().put("/reviews/1") .then().log().all() .assertThat() .apply(document("reviews/update/success")) @@ -238,7 +219,7 @@ void updateReview_noAuth() { .contentType(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer aws-cognito-access-token") .body(request) - .when().put("/courses/reviews/1") + .when().put("/reviews/1") .then().log().all() .assertThat() .apply(document("reviews/update/fail/noAuth")) @@ -255,7 +236,7 @@ void deleteReview() { restDocs .header("Authorization", "any") - .when().delete("/courses/reviews/8") + .when().delete("/reviews/8") .then().log().all() .assertThat() .apply(document("reviews/delete/success")) @@ -270,7 +251,7 @@ void deleteReview_noAuth() { restDocs .header("Authorization", "any") - .when().delete("/courses/reviews/8") + .when().delete("/reviews/8") .then().log().all() .assertThat() .apply(document("reviews/delete/fail/noAuth")) diff --git a/src/test/java/org/wooriverygood/api/review/application/ReviewFindServiceTest.java b/src/test/java/org/wooriverygood/api/review/application/ReviewFindServiceTest.java new file mode 100644 index 0000000..89c8ad0 --- /dev/null +++ b/src/test/java/org/wooriverygood/api/review/application/ReviewFindServiceTest.java @@ -0,0 +1,98 @@ +package org.wooriverygood.api.review.application; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.wooriverygood.api.course.domain.Course; +import org.wooriverygood.api.global.auth.AuthInfo; +import org.wooriverygood.api.review.domain.Review; +import org.wooriverygood.api.review.dto.ReviewsResponse; +import org.wooriverygood.api.review.repository.ReviewLikeRepository; +import org.wooriverygood.api.review.repository.ReviewRepository; +import org.wooriverygood.api.util.MockTest; + +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +class ReviewFindServiceTest extends MockTest { + + @InjectMocks + private ReviewFindService reviewFindService; + + @Mock + private ReviewRepository reviewRepository; + + @Mock + private ReviewLikeRepository reviewLikeRepository; + + private List reviews = new ArrayList<>(); + + private AuthInfo authInfo = AuthInfo.builder() + .sub("22222-34534-123") + .username("22222-34534-123") + .build(); + + private Course course = Course.builder() + .id(1L) + .course_name("Gaoshu") + .course_category("Zhuanye") + .course_credit(5) + .isYouguan(0) + .kaikeYuanxi("Xinke") + .build(); + + private final int REVIEW_COUNT = 10; + + + @BeforeEach + void setUp() { + for(long i = 0; i < REVIEW_COUNT; i++) { + Review review = Review.builder() + .id(i) + .course(course) + .reviewContent("review" + i) + .reviewTitle("review" + i) + .instructorName("jiaoshou") + .takenSemyr("22-23") + .grade("60") + .authorEmail("author" + i) + .reviewLikes(new ArrayList<>()) + .updated(false) + .build(); + reviews.add(review); + } + } + + @Test + @DisplayName("유효한 id를 통해 특정 수업의 리뷰들을 불러온다.") + void findAllReviewsByCourseId() { + when(reviewRepository.findAllByCourseId(any())) + .thenReturn(reviews); + when(reviewLikeRepository.existsByReviewAndUsername(any(Review.class), anyString())) + .thenReturn(true); + + ReviewsResponse response = reviewFindService.findAllReviewsByCourseId(1L, authInfo); + + assertThat(response.getReviews()).hasSize(REVIEW_COUNT); + assertThat(response.getReviews().get(0).getReviewTitle()).isEqualTo("review0"); + } + + @Test + @DisplayName("사용자 본인이 작성한 리뷰들을 불러온다.") + void findMyReviews() { + when(reviewRepository.findByAuthorEmail(any(String.class))) + .thenReturn(reviews); + + ReviewsResponse response = reviewFindService.findMyReviews(authInfo); + + assertThat(response.getReviews().get(0).isMine()).isEqualTo(true); + } + +} \ No newline at end of file diff --git a/src/test/java/org/wooriverygood/api/review/application/ReviewServiceTest.java b/src/test/java/org/wooriverygood/api/review/application/ReviewServiceTest.java index c21a54b..d537f81 100644 --- a/src/test/java/org/wooriverygood/api/review/application/ReviewServiceTest.java +++ b/src/test/java/org/wooriverygood/api/review/application/ReviewServiceTest.java @@ -11,7 +11,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.wooriverygood.api.global.error.exception.AuthorizationException; import org.wooriverygood.api.advice.exception.CourseNotFoundException; -import org.wooriverygood.api.course.domain.Courses; +import org.wooriverygood.api.course.domain.Course; import org.wooriverygood.api.course.repository.CourseRepository; import org.wooriverygood.api.review.domain.Review; import org.wooriverygood.api.review.domain.ReviewLike; @@ -44,7 +44,7 @@ public class ReviewServiceTest { List reviews = new ArrayList<>(); - Courses singleCourse= Courses.builder() + Course singleCourse= Course.builder() .id(1L) .course_name("Gaoshu") .course_category("Zhuanye") @@ -105,31 +105,6 @@ void setUpReviews() { .createdAt(LocalDateTime.of(2024, 1, 13, 12, 00)) .build(); - @Test - @DisplayName("유효한 id를 통해 특정 수업의 리뷰들을 불러온다.") - void findAllReviewsByCourseId() { - Mockito.when(courseRepository.findById(any())).thenReturn(Optional.ofNullable(singleCourse)); - Mockito.when(reviewRepository.findAllByCourseId(any())) - .thenReturn(reviews); - - List responses = reviewService.findAllReviewsByCourseId(1L, authInfo); - - Assertions.assertThat(responses).hasSize(REVIEW_COUNT); - Assertions.assertThat(responses.get(0).getReview_title()).isEqualTo("review0"); - - } - - @Test - @DisplayName("존재하지 않는 강의의 리뷰는 불러올 수 없다.") - void findAllReviewsByCourseId_noCourse() { - Mockito.when(courseRepository.findById(any())).thenThrow(CourseNotFoundException.class); - - Assertions.assertThatThrownBy(() -> reviewService.findAllReviewsByCourseId(99L, authInfo)) - .isInstanceOf(CourseNotFoundException.class); - - } - - @Test @DisplayName("특정 강의의 리뷰를 작성한다.") void addReview() { @@ -192,17 +167,6 @@ void likeReview_down() { Assertions.assertThat(response.isLiked()).isEqualTo(false); } - @Test - @DisplayName("사용자 본인이 작성한 리뷰들을 불러온다.") - void findMyReviews() { - Mockito.when(reviewRepository.findByAuthorEmail(any(String.class))) - .thenReturn(reviews); - - List responses = reviewService.findMyReviews(authInfo); - - Assertions.assertThat(responses.get(0).isMine()).isEqualTo(true); - } - @Test @DisplayName("권한이 있는 리뷰를 수정한다.") void updateReview() { diff --git a/src/test/java/org/wooriverygood/api/util/ApiTest.java b/src/test/java/org/wooriverygood/api/util/ApiTest.java index bfc9b35..1bb5fb1 100644 --- a/src/test/java/org/wooriverygood/api/util/ApiTest.java +++ b/src/test/java/org/wooriverygood/api/util/ApiTest.java @@ -20,6 +20,7 @@ import org.wooriverygood.api.course.service.CourseService; import org.wooriverygood.api.post.api.PostApi; import org.wooriverygood.api.review.api.ReviewApi; +import org.wooriverygood.api.review.application.ReviewFindService; import org.wooriverygood.api.review.application.ReviewValidateAccessService; import org.wooriverygood.api.review.application.ReviewService; import org.wooriverygood.api.global.auth.AuthInfo; @@ -51,6 +52,9 @@ public class ApiTest { @MockBean protected ReviewService reviewService; + @MockBean + protected ReviewFindService reviewFindService; + @MockBean protected ReviewValidateAccessService reviewValidateAccessService; From 03db49c08410f6bea11e3903d1ad5064eba745ab Mon Sep 17 00:00:00 2001 From: Park Geonho Date: Tue, 2 Apr 2024 01:44:36 +0800 Subject: [PATCH 10/21] =?UTF-8?q?[refactor]=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1/=EC=82=AD=EC=A0=9C=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 추가 - 리뷰 관련 api 엔드포인트 및 Response Body 변경 --- .../course/repository/CourseRepository.java | 14 +++ .../api/post/dto/NewPostRequest.java | 4 + .../api/review/api/ReviewApi.java | 20 +++-- .../application/ReviewCreateService.java | 45 ++++++++++ .../application/ReviewDeleteService.java | 35 ++++++++ .../api/review/application/ReviewService.java | 63 ------------- .../api/review/dto/NewReviewRequest.java | 29 ++++-- .../api/review/dto/NewReviewResponse.java | 27 ------ .../api/review/dto/ReviewDeleteResponse.java | 14 --- .../review/repository/ReviewRepository.java | 8 -- .../api/review/api/ReviewApiTest.java | 41 ++++----- .../application/ReviewCreateServiceTest.java | 67 ++++++++++++++ .../application/ReviewDeleteServiceTest.java | 89 +++++++++++++++++++ .../review/application/ReviewServiceTest.java | 64 ------------- .../org/wooriverygood/api/util/ApiTest.java | 10 ++- 15 files changed, 308 insertions(+), 222 deletions(-) create mode 100644 src/main/java/org/wooriverygood/api/review/application/ReviewCreateService.java create mode 100644 src/main/java/org/wooriverygood/api/review/application/ReviewDeleteService.java delete mode 100644 src/main/java/org/wooriverygood/api/review/dto/NewReviewResponse.java delete mode 100644 src/main/java/org/wooriverygood/api/review/dto/ReviewDeleteResponse.java create mode 100644 src/test/java/org/wooriverygood/api/review/application/ReviewCreateServiceTest.java create mode 100644 src/test/java/org/wooriverygood/api/review/application/ReviewDeleteServiceTest.java diff --git a/src/main/java/org/wooriverygood/api/course/repository/CourseRepository.java b/src/main/java/org/wooriverygood/api/course/repository/CourseRepository.java index ac666f1..5853702 100644 --- a/src/main/java/org/wooriverygood/api/course/repository/CourseRepository.java +++ b/src/main/java/org/wooriverygood/api/course/repository/CourseRepository.java @@ -1,7 +1,21 @@ package org.wooriverygood.api.course.repository; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.transaction.annotation.Transactional; import org.wooriverygood.api.course.domain.Course; public interface CourseRepository extends JpaRepository { + + @Transactional + @Modifying(clearAutomatically = true) + @Query(value = "UPDATE courses SET review_count = review_count + 1 WHERE course_id = :courseId", nativeQuery = true) + void increaseReviewCount(Long courseId); + + @Transactional + @Modifying(clearAutomatically = true) + @Query(value = "UPDATE courses SET review_count = review_count - 1 WHERE course_id = :courseId", nativeQuery = true) + void decreaseReviewCount(Long courseId); + } diff --git a/src/main/java/org/wooriverygood/api/post/dto/NewPostRequest.java b/src/main/java/org/wooriverygood/api/post/dto/NewPostRequest.java index dc76348..af57cbb 100644 --- a/src/main/java/org/wooriverygood/api/post/dto/NewPostRequest.java +++ b/src/main/java/org/wooriverygood/api/post/dto/NewPostRequest.java @@ -12,13 +12,17 @@ public class NewPostRequest { @NotBlank(message = "제목을 비우면 안됩니다.") private final String postTitle; + private final String postCategory; + private final String postContent; + @Builder public NewPostRequest(String postTitle, String postCategory, String postContent) { this.postTitle = postTitle; this.postCategory = postCategory; this.postContent = postContent; } + } diff --git a/src/main/java/org/wooriverygood/api/review/api/ReviewApi.java b/src/main/java/org/wooriverygood/api/review/api/ReviewApi.java index 48b1a69..7cd15a8 100644 --- a/src/main/java/org/wooriverygood/api/review/api/ReviewApi.java +++ b/src/main/java/org/wooriverygood/api/review/api/ReviewApi.java @@ -5,10 +5,8 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import org.wooriverygood.api.review.application.ReviewFindService; -import org.wooriverygood.api.review.application.ReviewValidateAccessService; +import org.wooriverygood.api.review.application.*; import org.wooriverygood.api.review.dto.*; -import org.wooriverygood.api.review.application.ReviewService; import org.wooriverygood.api.global.auth.AuthInfo; import org.wooriverygood.api.global.auth.Login; @@ -20,6 +18,10 @@ public class ReviewApi { private final ReviewService reviewService; + private final ReviewCreateService reviewCreateService; + + private final ReviewDeleteService reviewDeleteService; + private final ReviewFindService reviewFindService; private final ReviewValidateAccessService reviewValidateAccessService; @@ -34,9 +36,9 @@ public ResponseEntity findAllReviewsByCourseId(@PathVariable("i } @PostMapping("/courses/{id}/reviews") - public ResponseEntity addReview(@PathVariable("id") Long courseId, @Login AuthInfo authInfo, @Valid @RequestBody NewReviewRequest newReviewRequest) { - NewReviewResponse response = reviewService.addReview(authInfo, courseId, newReviewRequest); - return ResponseEntity.status(HttpStatus.CREATED).body(response); + public ResponseEntity addReview(@PathVariable("id") Long courseId, @Login AuthInfo authInfo, @Valid @RequestBody NewReviewRequest newReviewRequest) { + reviewCreateService.addReview(authInfo, courseId, newReviewRequest); + return ResponseEntity.status(HttpStatus.CREATED).build(); } @PutMapping("/reviews/{rid}/like") @@ -60,10 +62,10 @@ public ResponseEntity updateReview(@PathVariable("rid") Lo } @DeleteMapping("/reviews/{rid}") - public ResponseEntity deleteReview(@PathVariable("rid") Long reviewId, + public ResponseEntity deleteReview(@PathVariable("rid") Long reviewId, @Login AuthInfo authInfo) { - ReviewDeleteResponse response = reviewService.deleteReview(reviewId, authInfo); - return ResponseEntity.ok(response); + reviewDeleteService.deleteReview(reviewId, authInfo); + return ResponseEntity.noContent().build(); } } diff --git a/src/main/java/org/wooriverygood/api/review/application/ReviewCreateService.java b/src/main/java/org/wooriverygood/api/review/application/ReviewCreateService.java new file mode 100644 index 0000000..4a98851 --- /dev/null +++ b/src/main/java/org/wooriverygood/api/review/application/ReviewCreateService.java @@ -0,0 +1,45 @@ +package org.wooriverygood.api.review.application; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.wooriverygood.api.advice.exception.CourseNotFoundException; +import org.wooriverygood.api.course.domain.Course; +import org.wooriverygood.api.course.repository.CourseRepository; +import org.wooriverygood.api.global.auth.AuthInfo; +import org.wooriverygood.api.review.domain.Review; +import org.wooriverygood.api.review.dto.NewReviewRequest; +import org.wooriverygood.api.review.repository.ReviewRepository; + +@Service +@RequiredArgsConstructor +public class ReviewCreateService { + + private final CourseRepository courseRepository; + + private final ReviewRepository reviewRepository; + + + @Transactional + public void addReview(AuthInfo authInfo, Long courseId, NewReviewRequest request) { + Course course = courseRepository.findById(courseId) + .orElseThrow(CourseNotFoundException::new); + Review review = createReview(course, authInfo, request); + + reviewRepository.save(review); + courseRepository.increaseReviewCount(review.getCourse().getId()); + } + + private Review createReview(Course course, AuthInfo authInfo, NewReviewRequest request) { + return Review.builder() + .reviewTitle(request.getReviewTitle()) + .course(course) + .instructorName(request.getInstructorName()) + .takenSemyr(request.getTakenSemyr()) + .reviewContent(request.getReviewContent()) + .grade(request.getGrade()) + .authorEmail(authInfo.getUsername()) + .build(); + } + +} diff --git a/src/main/java/org/wooriverygood/api/review/application/ReviewDeleteService.java b/src/main/java/org/wooriverygood/api/review/application/ReviewDeleteService.java new file mode 100644 index 0000000..19e5c19 --- /dev/null +++ b/src/main/java/org/wooriverygood/api/review/application/ReviewDeleteService.java @@ -0,0 +1,35 @@ +package org.wooriverygood.api.review.application; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.wooriverygood.api.course.repository.CourseRepository; +import org.wooriverygood.api.global.auth.AuthInfo; +import org.wooriverygood.api.review.domain.Review; +import org.wooriverygood.api.review.exception.ReviewNotFoundException; +import org.wooriverygood.api.review.repository.ReviewLikeRepository; +import org.wooriverygood.api.review.repository.ReviewRepository; + +@Service +@RequiredArgsConstructor +public class ReviewDeleteService { + + private final CourseRepository courseRepository; + + private final ReviewRepository reviewRepository; + + private final ReviewLikeRepository reviewLikeRepository; + + + @Transactional + public void deleteReview(Long reviewId, AuthInfo authInfo) { + Review review = reviewRepository.findById(reviewId) + .orElseThrow(ReviewNotFoundException::new); + review.validateAuthor(authInfo.getUsername()); + + reviewLikeRepository.deleteAllByReview(review); + reviewRepository.delete(review); + courseRepository.decreaseReviewCount(review.getCourse().getId()); + } + +} diff --git a/src/main/java/org/wooriverygood/api/review/application/ReviewService.java b/src/main/java/org/wooriverygood/api/review/application/ReviewService.java index a0749fb..850b1bb 100644 --- a/src/main/java/org/wooriverygood/api/review/application/ReviewService.java +++ b/src/main/java/org/wooriverygood/api/review/application/ReviewService.java @@ -25,38 +25,6 @@ public class ReviewService { private final CourseRepository courseRepository; private final ReviewLikeRepository reviewLikeRepository; -// public List findAllReviewsByCourseId(Long courseId, AuthInfo authInfo) { -// Course course = courseRepository.findById(courseId) -// .orElseThrow(CourseNotFoundException::new); -// List reviews = reviewRepository.findAllByCourseId(courseId); -// -// if(authInfo.getUsername() == null) { -// return reviews.stream() -// .map(review -> ReviewResponse.of(review, false, reviewLikeRepository.existsByReviewAndUsername(review, authInfo.getUsername()))) -// .toList(); -// } -// return reviews.stream() -// .map(review -> ReviewResponse.of(review, review.isSameAuthor(authInfo.getUsername()), reviewLikeRepository.existsByReviewAndUsername(review, authInfo.getUsername()))) -// .toList(); -// } - - @Transactional - public NewReviewResponse addReview(AuthInfo authInfo, Long courseId, NewReviewRequest newReviewRequest) { - Course course = courseRepository.findById(courseId) - .orElseThrow(CourseNotFoundException::new); - Review review = Review.builder() - .reviewTitle(newReviewRequest.getReview_title()) - .course(course) - .instructorName(newReviewRequest.getInstructor_name()) - .takenSemyr(newReviewRequest.getTaken_semyr()) - .reviewContent(newReviewRequest.getReview_content()) - .grade(newReviewRequest.getGrade()) - .authorEmail(authInfo.getUsername()) - .build(); - Review saved = reviewRepository.save(review); - reviewRepository.increaseReviewCount(review.getCourse().getId()); - return createResponse(saved); - } @Transactional public ReviewLikeResponse likeReview(Long reviewId, AuthInfo authInfo) { @@ -91,24 +59,6 @@ private void deleteReviewLike(Review review, ReviewLike reviewLike) { reviewRepository.decreaseLikeCount(review.getId()); } - public List findMyReviews(AuthInfo authInfo) { - List reviews= reviewRepository.findByAuthorEmail(authInfo.getUsername()); - return reviews.stream().map(review -> ReviewResponse.of(review, true, reviewLikeRepository.existsByReviewAndUsername(review, authInfo.getUsername()))).toList(); - } - - - private NewReviewResponse createResponse(Review review) { - return NewReviewResponse.builder() - .review_id(review.getId()) - .review_title(review.getReviewTitle()) - .instructor_name(review.getInstructorName()) - .taken_semyr(review.getTakenSemyr()) - .review_content(review.getReviewContent()) - .grade(review.getGrade()) - .author_email(review.getAuthorEmail()) - .build(); - } - private ReviewLikeResponse createReviewLikeResponse(Review review, boolean liked) { int likeCount = review.getLikeCount() + (liked ? 1 : -1); return ReviewLikeResponse.builder() @@ -131,19 +81,6 @@ public ReviewUpdateResponse updateReview(Long reviewId, ReviewUpdateRequest revi .build(); } - @Transactional - public ReviewDeleteResponse deleteReview(Long reviewId, AuthInfo authInfo) { - Review review = reviewRepository.findById(reviewId) - .orElseThrow(ReviewNotFoundException::new); - review.validateAuthor(authInfo.getUsername()); - - reviewLikeRepository.deleteAllByReview(review); - reviewRepository.delete(review); - reviewRepository.decreaseReviewCount(review.getCourse().getId()); - return ReviewDeleteResponse.builder() - .review_id(reviewId) - .build(); - } } diff --git a/src/main/java/org/wooriverygood/api/review/dto/NewReviewRequest.java b/src/main/java/org/wooriverygood/api/review/dto/NewReviewRequest.java index 9069bbf..35cd273 100644 --- a/src/main/java/org/wooriverygood/api/review/dto/NewReviewRequest.java +++ b/src/main/java/org/wooriverygood/api/review/dto/NewReviewRequest.java @@ -1,22 +1,33 @@ package org.wooriverygood.api.review.dto; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; import lombok.Builder; import lombok.Getter; @Getter +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) public class NewReviewRequest { - private final String review_title; - private final String instructor_name; - private final String taken_semyr; - private final String review_content; + + private final String reviewTitle; + + private final String instructorName; + + private final String takenSemyr; + + private final String reviewContent; + private final String grade; + @Builder - public NewReviewRequest(String review_title, String instructor_name, String taken_semyr, String review_content, String grade) { - this.review_title = review_title; - this.instructor_name = instructor_name; - this.taken_semyr = taken_semyr; - this.review_content = review_content; + public NewReviewRequest(String reviewTitle, String instructorName, String takenSemyr, + String reviewContent, String grade) { + this.reviewTitle = reviewTitle; + this.instructorName = instructorName; + this.takenSemyr = takenSemyr; + this.reviewContent = reviewContent; this.grade = grade; } + } diff --git a/src/main/java/org/wooriverygood/api/review/dto/NewReviewResponse.java b/src/main/java/org/wooriverygood/api/review/dto/NewReviewResponse.java deleted file mode 100644 index 2ddc3f8..0000000 --- a/src/main/java/org/wooriverygood/api/review/dto/NewReviewResponse.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.wooriverygood.api.review.dto; - -import lombok.Builder; -import lombok.Getter; - -@Getter -public class NewReviewResponse { - private final Long review_id; - private final String review_title; - private final String instructor_name; - private final String taken_semyr; - private final String review_content; - private final String grade; - private final String author_email; - - - @Builder - public NewReviewResponse(Long review_id, String review_title, String instructor_name, String taken_semyr, String review_content, String grade, String author_email) { - this.review_id = review_id; - this.review_title = review_title; - this.instructor_name = instructor_name; - this.taken_semyr = taken_semyr; - this.review_content = review_content; - this.grade = grade; - this.author_email = author_email; - } -} diff --git a/src/main/java/org/wooriverygood/api/review/dto/ReviewDeleteResponse.java b/src/main/java/org/wooriverygood/api/review/dto/ReviewDeleteResponse.java deleted file mode 100644 index db9ffe4..0000000 --- a/src/main/java/org/wooriverygood/api/review/dto/ReviewDeleteResponse.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.wooriverygood.api.review.dto; - -import lombok.Builder; -import lombok.Getter; - -@Getter -public class ReviewDeleteResponse { - private final Long review_id; - - @Builder - public ReviewDeleteResponse(Long review_id) { - this.review_id = review_id; - } -} diff --git a/src/main/java/org/wooriverygood/api/review/repository/ReviewRepository.java b/src/main/java/org/wooriverygood/api/review/repository/ReviewRepository.java index 6dd3f7d..ef21a4e 100644 --- a/src/main/java/org/wooriverygood/api/review/repository/ReviewRepository.java +++ b/src/main/java/org/wooriverygood/api/review/repository/ReviewRepository.java @@ -29,13 +29,5 @@ public interface ReviewRepository extends JpaRepository { @Query(value = "UPDATE reviews SET like_count = like_count - 1 WHERE review_id = :reviewId", nativeQuery = true) void decreaseLikeCount(Long reviewId); - @Transactional - @Modifying(clearAutomatically = true) - @Query(value = "UPDATE courses SET review_count = review_count + 1 WHERE course_id = :courseId", nativeQuery = true) - void increaseReviewCount(Long courseId); - @Transactional - @Modifying(clearAutomatically = true) - @Query(value = "UPDATE courses SET review_count = review_count - 1 WHERE course_id = :courseId", nativeQuery = true) - void decreaseReviewCount(Long courseId); } diff --git a/src/test/java/org/wooriverygood/api/review/api/ReviewApiTest.java b/src/test/java/org/wooriverygood/api/review/api/ReviewApiTest.java index e56218e..e5bd9fb 100644 --- a/src/test/java/org/wooriverygood/api/review/api/ReviewApiTest.java +++ b/src/test/java/org/wooriverygood/api/review/api/ReviewApiTest.java @@ -96,25 +96,16 @@ void findAllReviewsByCourseId_exception_accessDenied() { @DisplayName("특정 강의의 리뷰를 작성한다.") void addReview() { NewReviewRequest request = NewReviewRequest.builder() - .review_title("Test Review from TestCode") - .instructor_name("Jiaoshou") - .taken_semyr("1stSem") - .review_content("Good!") + .reviewTitle("Test Review from TestCode") + .instructorName("Jiaoshou") + .takenSemyr("1stSem") + .reviewContent("Good!") .grade("100") .build(); - NewReviewResponse response = NewReviewResponse.builder() - .review_id(50L) - .author_email(authInfo.getUsername()) - .review_title("Test Review from TestCode") - .instructor_name("Jiaoshou") - .taken_semyr("1stSem") - .review_content("Good!") - .grade("100") - .build(); - - when(reviewService.addReview(any(AuthInfo.class), any(Long.class), any(NewReviewRequest.class))) - .thenReturn(response); + doNothing() + .when(reviewCreateService) + .addReview(any(AuthInfo.class), anyLong(), any(NewReviewRequest.class)); restDocs .contentType(MediaType.APPLICATION_JSON_VALUE) @@ -165,8 +156,8 @@ void findMyReviews() { .build()); } - when(reviewService.findMyReviews(any(AuthInfo.class))) - .thenReturn(responses); + when(reviewFindService.findMyReviews(any(AuthInfo.class))) + .thenReturn(new ReviewsResponse(responses)); restDocs .contentType(MediaType.APPLICATION_JSON_VALUE) @@ -229,10 +220,9 @@ void updateReview_noAuth() { @Test @DisplayName("권한이 있는 리뷰를 삭제한다.") void deleteReview() { - when(reviewService.deleteReview(any(Long.class), any(AuthInfo.class))) - .thenReturn(ReviewDeleteResponse.builder() - .review_id(8L) - .build()); + doNothing() + .when(reviewDeleteService) + .deleteReview(anyLong(), any(AuthInfo.class)); restDocs .header("Authorization", "any") @@ -240,14 +230,15 @@ void deleteReview() { .then().log().all() .assertThat() .apply(document("reviews/delete/success")) - .statusCode(HttpStatus.OK.value()); + .statusCode(HttpStatus.NO_CONTENT.value()); } @Test @DisplayName("권한이 없는 리뷰를 삭제하면 403을 반환한다.") void deleteReview_noAuth() { - when(reviewService.deleteReview(any(Long.class), any(AuthInfo.class))) - .thenThrow(new AuthorizationException()); + doThrow(new AuthorizationException()) + .when(reviewDeleteService) + .deleteReview(anyLong(), any(AuthInfo.class)); restDocs .header("Authorization", "any") diff --git a/src/test/java/org/wooriverygood/api/review/application/ReviewCreateServiceTest.java b/src/test/java/org/wooriverygood/api/review/application/ReviewCreateServiceTest.java new file mode 100644 index 0000000..6b555ba --- /dev/null +++ b/src/test/java/org/wooriverygood/api/review/application/ReviewCreateServiceTest.java @@ -0,0 +1,67 @@ +package org.wooriverygood.api.review.application; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.wooriverygood.api.course.domain.Course; +import org.wooriverygood.api.course.repository.CourseRepository; +import org.wooriverygood.api.global.auth.AuthInfo; +import org.wooriverygood.api.review.domain.Review; +import org.wooriverygood.api.review.dto.NewReviewRequest; +import org.wooriverygood.api.review.repository.ReviewRepository; +import org.wooriverygood.api.util.MockTest; + +import java.util.Optional; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.*; + +class ReviewCreateServiceTest extends MockTest { + + @InjectMocks + private ReviewCreateService reviewCreateService; + + @Mock + private CourseRepository courseRepository; + + @Mock + private ReviewRepository reviewRepository; + + private AuthInfo authInfo = AuthInfo.builder() + .sub("22222-34534-123") + .username("22222-34534-123") + .build(); + + private Course course = Course.builder() + .id(1L) + .course_name("Gaoshu") + .course_category("Zhuanye") + .course_credit(5) + .isYouguan(0) + .kaikeYuanxi("Xinke") + .build(); + + + @Test + @DisplayName("특정 강의의 리뷰를 작성한다.") + void addReview() { + NewReviewRequest request = NewReviewRequest.builder() + .reviewTitle("Test Review from TestCode") + .instructorName("Jiaoshou") + .takenSemyr("1stSem") + .reviewContent("Good!") + .grade("100") + .build(); + + when(courseRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(course)); + + reviewCreateService.addReview(authInfo, 1L, request); + + verify(reviewRepository).save(any(Review.class)); + verify(courseRepository).increaseReviewCount(course.getId()); + } + +} \ No newline at end of file diff --git a/src/test/java/org/wooriverygood/api/review/application/ReviewDeleteServiceTest.java b/src/test/java/org/wooriverygood/api/review/application/ReviewDeleteServiceTest.java new file mode 100644 index 0000000..bad8f86 --- /dev/null +++ b/src/test/java/org/wooriverygood/api/review/application/ReviewDeleteServiceTest.java @@ -0,0 +1,89 @@ +package org.wooriverygood.api.review.application; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.wooriverygood.api.course.domain.Course; +import org.wooriverygood.api.course.repository.CourseRepository; +import org.wooriverygood.api.global.auth.AuthInfo; +import org.wooriverygood.api.global.error.exception.AuthorizationException; +import org.wooriverygood.api.review.domain.Review; +import org.wooriverygood.api.review.exception.ReviewNotFoundException; +import org.wooriverygood.api.review.repository.ReviewLikeRepository; +import org.wooriverygood.api.review.repository.ReviewRepository; +import org.wooriverygood.api.util.MockTest; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +class ReviewDeleteServiceTest extends MockTest { + + @InjectMocks + private ReviewDeleteService reviewDeleteService; + + @Mock + private CourseRepository courseRepository; + + @Mock + private ReviewRepository reviewRepository; + + @Mock + private ReviewLikeRepository reviewLikeRepository; + + private Course course = Course.builder() + .id(1L) + .build(); + + private AuthInfo authInfo = AuthInfo.builder() + .sub("22222-34534-123") + .username("22222-34534-123") + .build(); + + @Mock + private Review review; + + @Test + @DisplayName("권한이 있는 리뷰를 삭제한다.") + void deleteReview() { + when(reviewRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(review)); + when(review.getCourse()) + .thenReturn(course); + + reviewDeleteService.deleteReview(1L, authInfo); + + verify(review).validateAuthor(authInfo.getUsername()); + verify(reviewLikeRepository).deleteAllByReview(review); + verify(reviewRepository).delete(review); + verify(courseRepository).decreaseReviewCount(course.getId()); + } + + @Test + @DisplayName("권한이 없는 리뷰는 삭제가 불가능하다.") + void deleteReview_exception_noAuth() { + when(reviewRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(review)); + doThrow(new AuthorizationException()) + .when(review) + .validateAuthor(anyString()); + + assertThatThrownBy(() -> reviewDeleteService.deleteReview(1L, authInfo)) + .isInstanceOf(AuthorizationException.class); + } + + @Test + @DisplayName("리뷰를 찾을 수 없으면, 예외를 발생시킨다.") + void deleteReview_exception_reviewNotFound() { + when(reviewRepository.findById(anyLong())) + .thenThrow(new ReviewNotFoundException()); + + assertThatThrownBy(() -> reviewDeleteService.deleteReview(1L, authInfo)) + .isInstanceOf(ReviewNotFoundException.class); + } + +} \ No newline at end of file diff --git a/src/test/java/org/wooriverygood/api/review/application/ReviewServiceTest.java b/src/test/java/org/wooriverygood/api/review/application/ReviewServiceTest.java index d537f81..f25604d 100644 --- a/src/test/java/org/wooriverygood/api/review/application/ReviewServiceTest.java +++ b/src/test/java/org/wooriverygood/api/review/application/ReviewServiceTest.java @@ -10,7 +10,6 @@ import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import org.wooriverygood.api.global.error.exception.AuthorizationException; -import org.wooriverygood.api.advice.exception.CourseNotFoundException; import org.wooriverygood.api.course.domain.Course; import org.wooriverygood.api.course.repository.CourseRepository; import org.wooriverygood.api.review.domain.Review; @@ -91,49 +90,6 @@ void setUpReviews() { .createdAt(LocalDateTime.of(2022, 6, 13, 12, 00)) .build(); - Review noAuthReview = Review.builder() - .id(1L) - .course(singleCourse) - .reviewContent("test review") - .reviewTitle("test review title") - .instructorName("jiaoshou") - .takenSemyr("22-23") - .grade("100") - .authorEmail("somerandom-username") - .reviewLikes(new ArrayList<>()) - .updated(false) - .createdAt(LocalDateTime.of(2024, 1, 13, 12, 00)) - .build(); - - @Test - @DisplayName("특정 강의의 리뷰를 작성한다.") - void addReview() { - NewReviewRequest newReviewRequest = NewReviewRequest.builder() - .review_title("Test Review from TestCode") - .instructor_name("Jiaoshou") - .taken_semyr("1stSem") - .review_content("Good!") - .grade("100") - .build(); - - Mockito.when(reviewRepository.save(any(Review.class))) - .thenReturn(Review.builder() - .authorEmail(authInfo.getUsername()) - .reviewTitle(newReviewRequest.getReview_title()) - .instructorName(newReviewRequest.getInstructor_name()) - .takenSemyr(newReviewRequest.getTaken_semyr()) - .reviewContent(newReviewRequest.getReview_content()) - .grade(newReviewRequest.getGrade()) - .build()); - - Mockito.when(courseRepository.findById(any())) - .thenReturn(Optional.ofNullable(singleCourse)); - - NewReviewResponse response = reviewService.addReview(authInfo, 1L, newReviewRequest); - - Assertions.assertThat(response.getAuthor_email()).isEqualTo(authInfo.getUsername()); - Assertions.assertThat(response.getReview_content()).isEqualTo(newReviewRequest.getReview_content()); - } @Test @DisplayName("특정 리뷰의 좋아요를 1 올린다.") @@ -202,24 +158,4 @@ void updateReview() { // .isInstanceOf(AuthorizationException.class); // } - @Test - @DisplayName("권한이 있는 리뷰를 삭제한다.") - void deleteReview() { - Mockito.when(reviewRepository.findById(any(Long.class))) - .thenReturn(Optional.ofNullable(singleReview)); - - ReviewDeleteResponse response = reviewService.deleteReview(singleReview.getId(), authInfo); - Assertions.assertThat(response.getReview_id()).isEqualTo(singleReview.getId()); } - - @Test - @DisplayName("권한이 없는 리뷰는 삭제가 불가능하다.") - void deleteReview_noAuth() { - Mockito.when(reviewRepository.findById(any(Long.class))) - .thenReturn(Optional.ofNullable(noAuthReview)); - - Assertions.assertThatThrownBy(() -> reviewService.deleteReview(noAuthReview.getId(), authInfo)) - .isInstanceOf(AuthorizationException.class); - } - -} diff --git a/src/test/java/org/wooriverygood/api/util/ApiTest.java b/src/test/java/org/wooriverygood/api/util/ApiTest.java index 1bb5fb1..b6a30a6 100644 --- a/src/test/java/org/wooriverygood/api/util/ApiTest.java +++ b/src/test/java/org/wooriverygood/api/util/ApiTest.java @@ -20,9 +20,7 @@ import org.wooriverygood.api.course.service.CourseService; import org.wooriverygood.api.post.api.PostApi; import org.wooriverygood.api.review.api.ReviewApi; -import org.wooriverygood.api.review.application.ReviewFindService; -import org.wooriverygood.api.review.application.ReviewValidateAccessService; -import org.wooriverygood.api.review.application.ReviewService; +import org.wooriverygood.api.review.application.*; import org.wooriverygood.api.global.auth.AuthInfo; import org.wooriverygood.api.global.auth.AuthenticationPrincipalArgumentResolver; @@ -55,6 +53,12 @@ public class ApiTest { @MockBean protected ReviewFindService reviewFindService; + @MockBean + protected ReviewCreateService reviewCreateService; + + @MockBean + protected ReviewDeleteService reviewDeleteService; + @MockBean protected ReviewValidateAccessService reviewValidateAccessService; From 7f73b1004fdd19fa6f87af281001f5614b820083 Mon Sep 17 00:00:00 2001 From: Park Geonho Date: Tue, 2 Apr 2024 02:19:09 +0800 Subject: [PATCH 11/21] =?UTF-8?q?[refactor]=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 추가 - 리뷰 관련 api 엔드포인트 변경 --- src/docs/asciidoc/index.adoc | 8 +- .../application/PostLikeToggleService.java | 5 +- .../api/review/api/ReviewApi.java | 34 ++-- ...vice.java => ReviewLikeToggleService.java} | 34 +--- .../application/ReviewUpdateService.java | 33 ++++ .../api/review/domain/Review.java | 35 ++-- .../api/review/dto/NewReviewRequest.java | 2 + .../api/review/dto/ReviewLikeResponse.java | 13 +- .../api/review/dto/ReviewUpdateRequest.java | 31 ++-- .../api/review/dto/ReviewUpdateResponse.java | 15 -- .../api/review/api/ReviewApiTest.java | 33 ++-- .../ReviewLikeToggleServiceTest.java | 87 ++++++++++ .../review/application/ReviewServiceTest.java | 161 ------------------ .../application/ReviewUpdateServiceTest.java | 77 +++++++++ .../org/wooriverygood/api/util/ApiTest.java | 5 +- 15 files changed, 307 insertions(+), 266 deletions(-) rename src/main/java/org/wooriverygood/api/review/application/{ReviewService.java => ReviewLikeToggleService.java} (63%) create mode 100644 src/main/java/org/wooriverygood/api/review/application/ReviewUpdateService.java delete mode 100644 src/main/java/org/wooriverygood/api/review/dto/ReviewUpdateResponse.java create mode 100644 src/test/java/org/wooriverygood/api/review/application/ReviewLikeToggleServiceTest.java delete mode 100644 src/test/java/org/wooriverygood/api/review/application/ReviewServiceTest.java create mode 100644 src/test/java/org/wooriverygood/api/review/application/ReviewUpdateServiceTest.java diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index 769b606..c6416eb 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -34,15 +34,15 @@ operation::reviews/find/fail/denied[snippets='http-request,http-response'] ==== 성공 operation::reviews/create/success[snippets='http-request,http-response'] -=== 리뷰 좋아요 (PUT /courses/reviews/{rid}/like) +=== 리뷰 좋아요 (PUT /reviews/{id}/like) ==== 성공 operation::reviews/like/success[snippets='http-request,http-response'] -=== 내 리뷰 보기 (GET /courses/reviews/me) +=== 내 리뷰 보기 (GET /reviews/me) ==== 성공 operation::reviews/find/me/success[snippets='http-request,http-response'] -=== 리뷰 수정 (PUT /courses/reviews/{rid}) +=== 리뷰 수정 (PUT /reviews/{id}) ==== 성공 operation::reviews/update/success[snippets='http-request,http-response'] @@ -50,7 +50,7 @@ operation::reviews/update/success[snippets='http-request,http-response'] ===== 권한이 없는 경우 operation::reviews/update/fail/noAuth[snippets='http-request,http-response'] -=== 리뷰 삭제 (DELETE /courses/reviews/{rid}) +=== 리뷰 삭제 (DELETE /reviews/{id}) ==== 성공 operation::reviews/delete/success[snippets='http-request,http-response'] diff --git a/src/main/java/org/wooriverygood/api/post/application/PostLikeToggleService.java b/src/main/java/org/wooriverygood/api/post/application/PostLikeToggleService.java index 0dcc9f9..e1536a0 100644 --- a/src/main/java/org/wooriverygood/api/post/application/PostLikeToggleService.java +++ b/src/main/java/org/wooriverygood/api/post/application/PostLikeToggleService.java @@ -18,9 +18,10 @@ @RequiredArgsConstructor public class PostLikeToggleService { - private PostRepository postRepository; + private final PostRepository postRepository; + + private final PostLikeRepository postLikeRepository; - private PostLikeRepository postLikeRepository; public PostLikeResponse togglePostLike(long postId, AuthInfo authInfo) { Post post = postRepository.findById(postId) diff --git a/src/main/java/org/wooriverygood/api/review/api/ReviewApi.java b/src/main/java/org/wooriverygood/api/review/api/ReviewApi.java index 7cd15a8..0fbdd99 100644 --- a/src/main/java/org/wooriverygood/api/review/api/ReviewApi.java +++ b/src/main/java/org/wooriverygood/api/review/api/ReviewApi.java @@ -10,13 +10,11 @@ import org.wooriverygood.api.global.auth.AuthInfo; import org.wooriverygood.api.global.auth.Login; -import java.util.List; - @RestController @RequiredArgsConstructor public class ReviewApi { - private final ReviewService reviewService; + private final ReviewLikeToggleService reviewLikeToggleService; private final ReviewCreateService reviewCreateService; @@ -24,6 +22,8 @@ public class ReviewApi { private final ReviewFindService reviewFindService; + private final ReviewUpdateService reviewUpdateService; + private final ReviewValidateAccessService reviewValidateAccessService; @@ -36,14 +36,16 @@ public ResponseEntity findAllReviewsByCourseId(@PathVariable("i } @PostMapping("/courses/{id}/reviews") - public ResponseEntity addReview(@PathVariable("id") Long courseId, @Login AuthInfo authInfo, @Valid @RequestBody NewReviewRequest newReviewRequest) { - reviewCreateService.addReview(authInfo, courseId, newReviewRequest); + public ResponseEntity addReview(@PathVariable("id") Long courseId, @Login AuthInfo authInfo, + @Valid @RequestBody NewReviewRequest request) { + reviewCreateService.addReview(authInfo, courseId, request); return ResponseEntity.status(HttpStatus.CREATED).build(); } @PutMapping("/reviews/{rid}/like") - public ResponseEntity likeReview(@PathVariable("rid") Long reviewId, @Login AuthInfo authInfo) { - ReviewLikeResponse response= reviewService.likeReview(reviewId, authInfo); + public ResponseEntity likeReview(@PathVariable("rid") Long reviewId, + @Login AuthInfo authInfo) { + ReviewLikeResponse response= reviewLikeToggleService.toggleReviewLike(reviewId, authInfo); return ResponseEntity.ok(response); } @@ -53,17 +55,17 @@ public ResponseEntity findMyReviews(@Login AuthInfo authInfo) { return ResponseEntity.ok(response); } - @PutMapping("/reviews/{rid}") - public ResponseEntity updateReview(@PathVariable("rid") Long reviewId, - @Valid @RequestBody ReviewUpdateRequest reviewUpdateRequest, - @Login AuthInfo authInfo) { - ReviewUpdateResponse response = reviewService.updateReview(reviewId, reviewUpdateRequest, authInfo); - return ResponseEntity.ok(response); + @PutMapping("/reviews/{id}") + public ResponseEntity updateReview(@PathVariable("id") Long reviewId, + @Valid @RequestBody ReviewUpdateRequest request, + @Login AuthInfo authInfo) { + reviewUpdateService.updateReview(reviewId, request, authInfo); + return ResponseEntity.noContent().build(); } - @DeleteMapping("/reviews/{rid}") - public ResponseEntity deleteReview(@PathVariable("rid") Long reviewId, - @Login AuthInfo authInfo) { + @DeleteMapping("/reviews/{id}") + public ResponseEntity deleteReview(@PathVariable("id") Long reviewId, + @Login AuthInfo authInfo) { reviewDeleteService.deleteReview(reviewId, authInfo); return ResponseEntity.noContent().build(); } diff --git a/src/main/java/org/wooriverygood/api/review/application/ReviewService.java b/src/main/java/org/wooriverygood/api/review/application/ReviewLikeToggleService.java similarity index 63% rename from src/main/java/org/wooriverygood/api/review/application/ReviewService.java rename to src/main/java/org/wooriverygood/api/review/application/ReviewLikeToggleService.java index 850b1bb..9128231 100644 --- a/src/main/java/org/wooriverygood/api/review/application/ReviewService.java +++ b/src/main/java/org/wooriverygood/api/review/application/ReviewLikeToggleService.java @@ -3,10 +3,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.wooriverygood.api.advice.exception.CourseNotFoundException; import org.wooriverygood.api.review.exception.ReviewNotFoundException; -import org.wooriverygood.api.course.domain.Course; -import org.wooriverygood.api.course.repository.CourseRepository; import org.wooriverygood.api.review.domain.Review; import org.wooriverygood.api.review.domain.ReviewLike; import org.wooriverygood.api.review.dto.*; @@ -14,26 +11,25 @@ import org.wooriverygood.api.review.repository.ReviewRepository; import org.wooriverygood.api.global.auth.AuthInfo; -import java.util.List; import java.util.Optional; @Service +@Transactional @RequiredArgsConstructor -@Transactional(readOnly = true) -public class ReviewService { +public class ReviewLikeToggleService { + private final ReviewRepository reviewRepository; - private final CourseRepository courseRepository; + private final ReviewLikeRepository reviewLikeRepository; - @Transactional - public ReviewLikeResponse likeReview(Long reviewId, AuthInfo authInfo) { + public ReviewLikeResponse toggleReviewLike(Long reviewId, AuthInfo authInfo) { Review review = reviewRepository.findById(reviewId) .orElseThrow(ReviewNotFoundException::new); Optional reviewLike = reviewLikeRepository.findByReviewAndUsername(review, authInfo.getUsername()); - if(reviewLike.isEmpty()) { + if (reviewLike.isEmpty()) { addReviewLike(review, authInfo.getUsername()); return createReviewLikeResponse(review, true); } @@ -62,25 +58,9 @@ private void deleteReviewLike(Review review, ReviewLike reviewLike) { private ReviewLikeResponse createReviewLikeResponse(Review review, boolean liked) { int likeCount = review.getLikeCount() + (liked ? 1 : -1); return ReviewLikeResponse.builder() - .like_count(likeCount) + .likeCount(likeCount) .liked(liked) .build(); } - - @Transactional - public ReviewUpdateResponse updateReview(Long reviewId, ReviewUpdateRequest reviewUpdateRequest, AuthInfo authInfo) { - Review review = reviewRepository.findById(reviewId) - .orElseThrow(ReviewNotFoundException::new); - review.validateAuthor(authInfo.getUsername()); - - review.updateReview(reviewUpdateRequest.getReview_title(), reviewUpdateRequest.getInstructor_name(), reviewUpdateRequest.getTaken_semyr(), reviewUpdateRequest.getReview_content(), reviewUpdateRequest.getGrade(), authInfo.getUsername()); - - return ReviewUpdateResponse.builder() - .review_id(review.getId()) - .build(); - } - - - } diff --git a/src/main/java/org/wooriverygood/api/review/application/ReviewUpdateService.java b/src/main/java/org/wooriverygood/api/review/application/ReviewUpdateService.java new file mode 100644 index 0000000..2234fc9 --- /dev/null +++ b/src/main/java/org/wooriverygood/api/review/application/ReviewUpdateService.java @@ -0,0 +1,33 @@ +package org.wooriverygood.api.review.application; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.wooriverygood.api.global.auth.AuthInfo; +import org.wooriverygood.api.review.domain.Review; +import org.wooriverygood.api.review.dto.ReviewUpdateRequest; +import org.wooriverygood.api.review.exception.ReviewNotFoundException; +import org.wooriverygood.api.review.repository.ReviewRepository; + +@Service +@RequiredArgsConstructor +public class ReviewUpdateService { + + private final ReviewRepository reviewRepository; + + + @Transactional + public void updateReview(Long reviewId, ReviewUpdateRequest request, AuthInfo authInfo) { + Review review = reviewRepository.findById(reviewId) + .orElseThrow(ReviewNotFoundException::new); + review.validateAuthor(authInfo.getUsername()); + + review.updateTitle(request.getReviewTitle()); + review.updateInstructorName(request.getInstructorName()); + review.updateTakenSemyr(request.getTakenSemyr()); + review.updateContent(request.getReviewContent()); + review.updateGrade(request.getGrade()); + review.updateAuthor(authInfo.getUsername()); + } + +} diff --git a/src/main/java/org/wooriverygood/api/review/domain/Review.java b/src/main/java/org/wooriverygood/api/review/domain/Review.java index b361cd8..787f5f8 100644 --- a/src/main/java/org/wooriverygood/api/review/domain/Review.java +++ b/src/main/java/org/wooriverygood/api/review/domain/Review.java @@ -94,19 +94,34 @@ public void deleteReviewLike(ReviewLike reviewLike) { reviewLike.delete(); } - public void updateReview(String title, String instructorName, String takenSemyr, String content, String grade, String author) { - this.reviewTitle = title; - this.reviewContent = content; - this.instructorName = instructorName; + public void updateTitle(String title) { + reviewTitle = title; + updated = true; + } + + public void updateContent(String content) { + reviewContent = content; + updated = true; + } + + public void updateTakenSemyr(String takenSemyr) { this.takenSemyr = takenSemyr; + updated = true; + } + + public void updateInstructorName(String instructorName) { + this.instructorName = instructorName; + updated = true; + } + + public void updateGrade(String grade) { this.grade = grade; updated = true; - if (id <= 234) { - authorEmail = author; - } - if (createdAt == null) { - createdAt = LocalDateTime.now(); - } + } + + public void updateAuthor(String author) { + authorEmail = author; + updated = true; } } diff --git a/src/main/java/org/wooriverygood/api/review/dto/NewReviewRequest.java b/src/main/java/org/wooriverygood/api/review/dto/NewReviewRequest.java index 35cd273..c036373 100644 --- a/src/main/java/org/wooriverygood/api/review/dto/NewReviewRequest.java +++ b/src/main/java/org/wooriverygood/api/review/dto/NewReviewRequest.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.databind.annotation.JsonNaming; +import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; @@ -9,6 +10,7 @@ @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) public class NewReviewRequest { + @NotBlank(message = "제목이 없습니다.") private final String reviewTitle; private final String instructorName; diff --git a/src/main/java/org/wooriverygood/api/review/dto/ReviewLikeResponse.java b/src/main/java/org/wooriverygood/api/review/dto/ReviewLikeResponse.java index 644a8c4..9af6e66 100644 --- a/src/main/java/org/wooriverygood/api/review/dto/ReviewLikeResponse.java +++ b/src/main/java/org/wooriverygood/api/review/dto/ReviewLikeResponse.java @@ -1,16 +1,23 @@ package org.wooriverygood.api.review.dto; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; import lombok.Builder; import lombok.Getter; @Getter +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) public class ReviewLikeResponse { - private final int like_count; + + private final int likeCount; + private final boolean liked; + @Builder - public ReviewLikeResponse(int like_count, boolean liked) { - this.like_count = like_count; + public ReviewLikeResponse(int likeCount, boolean liked) { + this.likeCount = likeCount; this.liked =liked; } + } diff --git a/src/main/java/org/wooriverygood/api/review/dto/ReviewUpdateRequest.java b/src/main/java/org/wooriverygood/api/review/dto/ReviewUpdateRequest.java index 7590046..8d2de4a 100644 --- a/src/main/java/org/wooriverygood/api/review/dto/ReviewUpdateRequest.java +++ b/src/main/java/org/wooriverygood/api/review/dto/ReviewUpdateRequest.java @@ -1,26 +1,35 @@ package org.wooriverygood.api.review.dto; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; -import lombok.NoArgsConstructor; @Getter -@NoArgsConstructor +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) public class ReviewUpdateRequest { + @NotBlank(message = "제목이 없습니다.") - private String review_title; - private String instructor_name; - private String taken_semyr; - private String review_content; + private String reviewTitle; + + private String instructorName; + + private String takenSemyr; + + private String reviewContent; + private String grade; + @Builder - public ReviewUpdateRequest(String review_title, String instructor_name, String taken_semyr, String review_content, String grade) { - this.review_title = review_title; - this.instructor_name = instructor_name; - this.taken_semyr = taken_semyr; - this.review_content = review_content; + public ReviewUpdateRequest(String reviewTitle, String instructorName, String takenSemyr, + String reviewContent, String grade) { + this.reviewTitle = reviewTitle; + this.instructorName = instructorName; + this.takenSemyr = takenSemyr; + this.reviewContent = reviewContent; this.grade = grade; } + } diff --git a/src/main/java/org/wooriverygood/api/review/dto/ReviewUpdateResponse.java b/src/main/java/org/wooriverygood/api/review/dto/ReviewUpdateResponse.java deleted file mode 100644 index fb0399c..0000000 --- a/src/main/java/org/wooriverygood/api/review/dto/ReviewUpdateResponse.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.wooriverygood.api.review.dto; - -import lombok.Builder; -import lombok.Getter; - -@Getter -public class ReviewUpdateResponse { - - private final Long review_id; - - @Builder - public ReviewUpdateResponse(Long review_id) { - this.review_id = review_id; - } -} diff --git a/src/test/java/org/wooriverygood/api/review/api/ReviewApiTest.java b/src/test/java/org/wooriverygood/api/review/api/ReviewApiTest.java index e5bd9fb..087950e 100644 --- a/src/test/java/org/wooriverygood/api/review/api/ReviewApiTest.java +++ b/src/test/java/org/wooriverygood/api/review/api/ReviewApiTest.java @@ -6,7 +6,6 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.wooriverygood.api.global.error.exception.AuthorizationException; -import org.wooriverygood.api.advice.exception.CourseNotFoundException; import org.wooriverygood.api.review.exception.ReviewAccessDeniedException; import org.wooriverygood.api.course.domain.Course; import org.wooriverygood.api.review.dto.*; @@ -40,6 +39,7 @@ public class ReviewApiTest extends ApiTest { .username("22222-34534-123") .build(); + @BeforeEach void setUp() { for(int i = 1; i <= 2; i++) { @@ -121,9 +121,9 @@ void addReview() { @Test @DisplayName("특정 리뷰의 좋아요를 1 올리거나 내린다.") void likeReview() { - when(reviewService.likeReview(any(Long.class), any(AuthInfo.class))) + when(reviewLikeToggleService.toggleReviewLike(any(Long.class), any(AuthInfo.class))) .thenReturn(ReviewLikeResponse.builder() - .like_count(5) + .likeCount(5) .liked(false) .build()); @@ -173,17 +173,17 @@ void findMyReviews() { @DisplayName("권한이 있는 리뷰를 수정한다.") void updateReview() { ReviewUpdateRequest request = ReviewUpdateRequest.builder() - .review_title("new title") - .review_content("new content") - .instructor_name("jiaoshou") - .taken_semyr("18-19") + .reviewTitle("new title") + .reviewContent("new content") + .instructorName("jiaoshou") + .takenSemyr("18-19") .grade("100") .build(); - when(reviewService.updateReview(any(Long.class), any(ReviewUpdateRequest.class), any(AuthInfo.class))) - .thenReturn(ReviewUpdateResponse.builder() - .review_id((long)1) - .build()); + doNothing() + .when(reviewUpdateService) + .updateReview(anyLong(), any(ReviewUpdateRequest.class), any(AuthInfo.class)); + restDocs .contentType(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer aws-cognito-access-token") @@ -192,19 +192,20 @@ void updateReview() { .then().log().all() .assertThat() .apply(document("reviews/update/success")) - .statusCode(HttpStatus.OK.value()); + .statusCode(HttpStatus.NO_CONTENT.value()); } @Test @DisplayName("권한이 없는 리뷰를 수정하면 403을 반환한다.") void updateReview_noAuth() { ReviewUpdateRequest request = ReviewUpdateRequest.builder() - .review_title("new title") - .review_content("new content") + .reviewTitle("new title") + .reviewContent("new content") .build(); - when(reviewService.updateReview(any(Long.class), any(ReviewUpdateRequest.class), any(AuthInfo.class))) - .thenThrow(new AuthorizationException()); + doThrow(new AuthorizationException()) + .when(reviewUpdateService) + .updateReview(anyLong(), any(ReviewUpdateRequest.class), any(AuthInfo.class)); restDocs .contentType(MediaType.APPLICATION_JSON_VALUE) diff --git a/src/test/java/org/wooriverygood/api/review/application/ReviewLikeToggleServiceTest.java b/src/test/java/org/wooriverygood/api/review/application/ReviewLikeToggleServiceTest.java new file mode 100644 index 0000000..430a4ad --- /dev/null +++ b/src/test/java/org/wooriverygood/api/review/application/ReviewLikeToggleServiceTest.java @@ -0,0 +1,87 @@ +package org.wooriverygood.api.review.application; + +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.wooriverygood.api.review.domain.Review; +import org.wooriverygood.api.review.domain.ReviewLike; +import org.wooriverygood.api.review.dto.*; +import org.wooriverygood.api.review.repository.ReviewLikeRepository; +import org.wooriverygood.api.review.repository.ReviewRepository; +import org.wooriverygood.api.global.auth.AuthInfo; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class ReviewLikeToggleServiceTest { + + @InjectMocks + private ReviewLikeToggleService reviewLikeToggleService; + + @Mock + private ReviewRepository reviewRepository; + + @Mock + private ReviewLikeRepository reviewLikeRepository; + + private final AuthInfo authInfo = AuthInfo.builder() + .sub("22222-34534-123") + .username("22222-34534-123") + .build(); + + private final Review review = Review.builder() + .id(1L) + .reviewContent("test review") + .reviewTitle("test review title") + .instructorName("jiaoshou") + .takenSemyr("22-23") + .grade("100") + .authorEmail(authInfo.getUsername()) + .reviewLikes(new ArrayList<>()) + .updated(false) + .createdAt(LocalDateTime.of(2022, 6, 13, 12, 00)) + .build(); + + + @Test + @DisplayName("특정 리뷰의 좋아요를 1 올린다.") + void likeReview_up() { + when(reviewRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(review)); + + ReviewLikeResponse response = reviewLikeToggleService.toggleReviewLike(review.getId(), authInfo); + + assertThat(response.getLikeCount()).isEqualTo(review.getLikeCount() + 1); + assertThat(response.isLiked()).isEqualTo(true); + } + + @Test + @DisplayName("특정 리뷰의 좋아요를 1 내린다.") + void likeReview_down() { + ReviewLike reviewLike = ReviewLike.builder() + .id(3L) + .review(review) + .username(authInfo.getUsername()) + .build(); + + when(reviewRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(review)); + when(reviewLikeRepository.findByReviewAndUsername(any(Review.class), anyString())) + .thenReturn(Optional.ofNullable(reviewLike)); + + ReviewLikeResponse response = reviewLikeToggleService.toggleReviewLike(review.getId(), authInfo); + + assertThat(response.getLikeCount()).isEqualTo(review.getLikeCount() - 1); + assertThat(response.isLiked()).isEqualTo(false); + } + +} diff --git a/src/test/java/org/wooriverygood/api/review/application/ReviewServiceTest.java b/src/test/java/org/wooriverygood/api/review/application/ReviewServiceTest.java deleted file mode 100644 index f25604d..0000000 --- a/src/test/java/org/wooriverygood/api/review/application/ReviewServiceTest.java +++ /dev/null @@ -1,161 +0,0 @@ -package org.wooriverygood.api.review.application; - -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -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.Mockito; -import org.mockito.junit.jupiter.MockitoExtension; -import org.wooriverygood.api.global.error.exception.AuthorizationException; -import org.wooriverygood.api.course.domain.Course; -import org.wooriverygood.api.course.repository.CourseRepository; -import org.wooriverygood.api.review.domain.Review; -import org.wooriverygood.api.review.domain.ReviewLike; -import org.wooriverygood.api.review.dto.*; -import org.wooriverygood.api.review.repository.ReviewLikeRepository; -import org.wooriverygood.api.review.repository.ReviewRepository; -import org.wooriverygood.api.global.auth.AuthInfo; - -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import static org.mockito.ArgumentMatchers.any; - -@ExtendWith(MockitoExtension.class) -public class ReviewServiceTest { - @InjectMocks - private ReviewService reviewService; - - @Mock - private ReviewRepository reviewRepository; - - @Mock - private CourseRepository courseRepository; - - @Mock - private ReviewLikeRepository reviewLikeRepository; - private final int REVIEW_COUNT = 10; - - List reviews = new ArrayList<>(); - - Course singleCourse= Course.builder() - .id(1L) - .course_name("Gaoshu") - .course_category("Zhuanye") - .course_credit(5) - .isYouguan(0) - .kaikeYuanxi("Xinke") - .build(); - - AuthInfo authInfo = AuthInfo.builder() - .sub("22222-34534-123") - .username("22222-34534-123") - .build(); - - @BeforeEach - void setUpReviews() { - for(int i = 0; i()) - .updated(false) - .build(); - reviews.add(review); - } - } - - Review singleReview = Review.builder() - .id(1L) - .course(singleCourse) - .reviewContent("test review") - .reviewTitle("test review title") - .instructorName("jiaoshou") - .takenSemyr("22-23") - .grade("100") - .authorEmail(authInfo.getUsername()) - .reviewLikes(new ArrayList<>()) - .updated(false) - .createdAt(LocalDateTime.of(2022, 6, 13, 12, 00)) - .build(); - - - @Test - @DisplayName("특정 리뷰의 좋아요를 1 올린다.") - void likeReview_up() { - Mockito.when(reviewRepository.findById(any(Long.class))) - .thenReturn(Optional.ofNullable(singleReview)); - - ReviewLikeResponse response = reviewService.likeReview(singleReview.getId(), authInfo); - - Assertions.assertThat(response.getLike_count()).isEqualTo(singleReview.getLikeCount() + 1); - Assertions.assertThat(response.isLiked()).isEqualTo(true); - } - - @Test - @DisplayName("특정 리뷰의 좋아요를 1 내린다.") - void likeReview_down() { - ReviewLike reviewLike = ReviewLike.builder() - .id(3L) - .review(singleReview) - .username(authInfo.getUsername()) - .build(); - - Mockito.when(reviewRepository.findById(any(Long.class))) - .thenReturn(Optional.ofNullable(singleReview)); - Mockito.when(reviewLikeRepository.findByReviewAndUsername(any(Review.class), any(String.class))) - .thenReturn(Optional.ofNullable(reviewLike)); - - ReviewLikeResponse response = reviewService.likeReview(singleReview.getId(), authInfo); - - Assertions.assertThat(response.getLike_count()).isEqualTo(singleReview.getLikeCount() - 1); - Assertions.assertThat(response.isLiked()).isEqualTo(false); - } - - @Test - @DisplayName("권한이 있는 리뷰를 수정한다.") - void updateReview() { - ReviewUpdateRequest request = ReviewUpdateRequest.builder() - .review_title("new title") - .review_content("new content") - .instructor_name("jiaoshou") - .taken_semyr("18-19") - .grade("100") - .build(); - - Mockito.when(reviewRepository.findById(any(Long.class))) - .thenReturn(Optional.ofNullable(singleReview)); - - ReviewUpdateResponse response = reviewService.updateReview(singleReview.getId(), request, authInfo); - - Assertions.assertThat(response.getReview_id()).isEqualTo(singleReview.getId()); - Assertions.assertThat(singleReview.isUpdated()).isEqualTo(true); - } - -// @Test -// @DisplayName("권한이 없는 리뷰는 수정이 불가능하다.") -// void updateReview_noAuth() { -// ReviewUpdateRequest request = ReviewUpdateRequest.builder() -// .review_title("new title") -// .review_content("new content") -// .build(); -// -// Mockito.when(reviewRepository.findById(any(Long.class))) -// .thenReturn(Optional.ofNullable(noAuthReview)); -// -// Assertions.assertThatThrownBy(() -> reviewService.updateReview(noAuthReview.getId(), request, authInfo)) -// .isInstanceOf(AuthorizationException.class); -// } - - } diff --git a/src/test/java/org/wooriverygood/api/review/application/ReviewUpdateServiceTest.java b/src/test/java/org/wooriverygood/api/review/application/ReviewUpdateServiceTest.java new file mode 100644 index 0000000..86a4cbe --- /dev/null +++ b/src/test/java/org/wooriverygood/api/review/application/ReviewUpdateServiceTest.java @@ -0,0 +1,77 @@ +package org.wooriverygood.api.review.application; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.wooriverygood.api.global.auth.AuthInfo; +import org.wooriverygood.api.global.error.exception.AuthorizationException; +import org.wooriverygood.api.review.domain.Review; +import org.wooriverygood.api.review.dto.ReviewUpdateRequest; +import org.wooriverygood.api.review.repository.ReviewRepository; +import org.wooriverygood.api.util.MockTest; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +class ReviewUpdateServiceTest extends MockTest { + + @InjectMocks + private ReviewUpdateService reviewUpdateService; + + @Mock + private ReviewRepository reviewRepository; + + private AuthInfo authInfo = AuthInfo.builder() + .sub("22222-34534-123") + .username("22222-34534-123") + .build(); + + @Mock + private Review review; + + @Test + @DisplayName("권한이 있는 리뷰를 수정한다.") + void updateReview() { + ReviewUpdateRequest request = ReviewUpdateRequest.builder() + .reviewTitle("new title") + .reviewContent("new content") + .instructorName("jiaoshou") + .takenSemyr("18-19") + .grade("100") + .build(); + + when(reviewRepository.findById(any(Long.class))) + .thenReturn(Optional.ofNullable(review)); + + reviewUpdateService.updateReview(1L, request, authInfo); + + verify(review).validateAuthor(authInfo.getUsername()); + verify(review).updateTitle(request.getReviewTitle()); + verify(review).updateInstructorName(request.getInstructorName()); + verify(review).updateTakenSemyr(request.getTakenSemyr()); + verify(review).updateContent(request.getReviewContent()); + verify(review).updateGrade(request.getGrade()); + verify(review).updateAuthor(authInfo.getUsername()); + } + + @Test + @DisplayName("권한이 없는 리뷰는 수정이 불가능하다.") + void updateReview_noAuth() { + ReviewUpdateRequest request = ReviewUpdateRequest.builder() + .build(); + + when(reviewRepository.findById(any(Long.class))) + .thenReturn(Optional.ofNullable(review)); + doThrow(new AuthorizationException()) + .when(review) + .validateAuthor(anyString()); + + assertThatThrownBy(() -> reviewUpdateService.updateReview(1L, request, authInfo)) + .isInstanceOf(AuthorizationException.class); + } + +} \ No newline at end of file diff --git a/src/test/java/org/wooriverygood/api/util/ApiTest.java b/src/test/java/org/wooriverygood/api/util/ApiTest.java index b6a30a6..1dbe89f 100644 --- a/src/test/java/org/wooriverygood/api/util/ApiTest.java +++ b/src/test/java/org/wooriverygood/api/util/ApiTest.java @@ -48,7 +48,7 @@ public class ApiTest { protected CourseService courseService; @MockBean - protected ReviewService reviewService; + protected ReviewLikeToggleService reviewLikeToggleService; @MockBean protected ReviewFindService reviewFindService; @@ -59,6 +59,9 @@ public class ApiTest { @MockBean protected ReviewDeleteService reviewDeleteService; + @MockBean + protected ReviewUpdateService reviewUpdateService; + @MockBean protected ReviewValidateAccessService reviewValidateAccessService; From 3807b477828b402ea0ed97cbd3f917eb92cd6e9e Mon Sep 17 00:00:00 2001 From: Park Geonho Date: Tue, 2 Apr 2024 16:03:41 +0800 Subject: [PATCH 12/21] =?UTF-8?q?[refactor]=20=EA=B0=95=EC=9D=98=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 추가 - 강의 관련 api Response Body 변경 --- .idea/workspace.xml | 115 ++++++++++++----- .../api/course/api/CourseApi.java | 40 ++++++ .../application/CourseCreateService.java | 34 +++++ .../course/application/CourseFindService.java | 38 ++++++ .../course/controller/CourseController.java | 39 ------ .../api/course/domain/Course.java | 17 ++- .../api/course/dto/CourseNameResponse.java | 13 +- .../api/course/dto/CourseResponse.java | 40 +++--- .../api/course/dto/CoursesResponse.java | 17 +++ .../api/course/dto/NewCourseRequest.java | 24 ++-- .../api/course/dto/NewCourseResponse.java | 24 ---- .../exception/CourseNotFoundException.java | 2 +- .../api/course/service/CourseService.java | 64 ---------- .../application/ReviewCreateService.java | 2 +- .../{controller => api}/CourseApiTest.java | 35 +++--- .../application/CourseCreateServiceTest.java | 40 ++++++ .../application/CourseFindServiceTest.java | 81 ++++++++++++ .../api/course/service/CourseServiceTest.java | 116 ------------------ .../api/post/api/PostApiTest.java | 8 -- .../api/review/api/ReviewApiTest.java | 18 +-- .../application/ReviewCreateServiceTest.java | 6 +- .../application/ReviewFindServiceTest.java | 6 +- .../org/wooriverygood/api/util/ApiTest.java | 12 +- 23 files changed, 429 insertions(+), 362 deletions(-) create mode 100644 src/main/java/org/wooriverygood/api/course/api/CourseApi.java create mode 100644 src/main/java/org/wooriverygood/api/course/application/CourseCreateService.java create mode 100644 src/main/java/org/wooriverygood/api/course/application/CourseFindService.java delete mode 100644 src/main/java/org/wooriverygood/api/course/controller/CourseController.java create mode 100644 src/main/java/org/wooriverygood/api/course/dto/CoursesResponse.java delete mode 100644 src/main/java/org/wooriverygood/api/course/dto/NewCourseResponse.java rename src/main/java/org/wooriverygood/api/{advice => course}/exception/CourseNotFoundException.java (85%) delete mode 100644 src/main/java/org/wooriverygood/api/course/service/CourseService.java rename src/test/java/org/wooriverygood/api/course/{controller => api}/CourseApiTest.java (75%) create mode 100644 src/test/java/org/wooriverygood/api/course/application/CourseCreateServiceTest.java create mode 100644 src/test/java/org/wooriverygood/api/course/application/CourseFindServiceTest.java delete mode 100644 src/test/java/org/wooriverygood/api/course/service/CourseServiceTest.java diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 0a390f5..2273a1f 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -4,9 +4,26 @@ - - - @@ -789,7 +837,10 @@ - diff --git a/src/main/java/org/wooriverygood/api/course/api/CourseApi.java b/src/main/java/org/wooriverygood/api/course/api/CourseApi.java new file mode 100644 index 0000000..f69b9d2 --- /dev/null +++ b/src/main/java/org/wooriverygood/api/course/api/CourseApi.java @@ -0,0 +1,40 @@ +package org.wooriverygood.api.course.api; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.wooriverygood.api.course.application.CourseFindService; +import org.wooriverygood.api.course.dto.*; +import org.wooriverygood.api.course.application.CourseCreateService; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/courses") +public class CourseApi { + + private final CourseCreateService courseCreateService; + + private final CourseFindService courseFindService; + + + @GetMapping + public ResponseEntity findAllCourses() { + CoursesResponse response = courseFindService.findAll(); + return ResponseEntity.ok(response); + } + + @PostMapping + public ResponseEntity addCourse(@Valid @RequestBody NewCourseRequest newCourseRequest) { + courseCreateService.addCourse(newCourseRequest); + return ResponseEntity.status(HttpStatus.CREATED).build(); + } + + @GetMapping("/{id}/name") + public ResponseEntity findCourseName(@PathVariable("id") Long courseId) { + CourseNameResponse response = courseFindService.findCourseName(courseId); + return ResponseEntity.ok(response); + } + +} diff --git a/src/main/java/org/wooriverygood/api/course/application/CourseCreateService.java b/src/main/java/org/wooriverygood/api/course/application/CourseCreateService.java new file mode 100644 index 0000000..98dbd0a --- /dev/null +++ b/src/main/java/org/wooriverygood/api/course/application/CourseCreateService.java @@ -0,0 +1,34 @@ +package org.wooriverygood.api.course.application; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.wooriverygood.api.course.domain.Course; +import org.wooriverygood.api.course.dto.NewCourseRequest; +import org.wooriverygood.api.course.repository.CourseRepository; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class CourseCreateService { + + private final CourseRepository courseRepository; + + + @Transactional + public void addCourse(NewCourseRequest request) { + Course course = createCourse(request); + courseRepository.save(course); + } + + private Course createCourse(NewCourseRequest request) { + return Course.builder() + .name(request.getCourseName()) + .category(request.getCourseCategory()) + .credit(request.getCourseCredit()) + .isYouguan(request.getIsYouguan()) + .kaikeYuanxi(request.getKaikeYuanxi()) + .build(); + } + +} diff --git a/src/main/java/org/wooriverygood/api/course/application/CourseFindService.java b/src/main/java/org/wooriverygood/api/course/application/CourseFindService.java new file mode 100644 index 0000000..b9875ae --- /dev/null +++ b/src/main/java/org/wooriverygood/api/course/application/CourseFindService.java @@ -0,0 +1,38 @@ +package org.wooriverygood.api.course.application; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.wooriverygood.api.course.exception.CourseNotFoundException; +import org.wooriverygood.api.course.domain.Course; +import org.wooriverygood.api.course.dto.CourseNameResponse; +import org.wooriverygood.api.course.dto.CourseResponse; +import org.wooriverygood.api.course.dto.CoursesResponse; +import org.wooriverygood.api.course.repository.CourseRepository; + +import java.util.List; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class CourseFindService { + + private final CourseRepository courseRepository; + + public CoursesResponse findAll() { + List responses = courseRepository.findAll() + .stream() + .map(CourseResponse::of) + .toList(); + + return new CoursesResponse(responses); + } + + public CourseNameResponse findCourseName(Long courseId) { + Course course = courseRepository.findById(courseId) + .orElseThrow(CourseNotFoundException::new); + + return new CourseNameResponse(course.getName()); + } + +} diff --git a/src/main/java/org/wooriverygood/api/course/controller/CourseController.java b/src/main/java/org/wooriverygood/api/course/controller/CourseController.java deleted file mode 100644 index 01f2baa..0000000 --- a/src/main/java/org/wooriverygood/api/course/controller/CourseController.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.wooriverygood.api.course.controller; - -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; -import org.wooriverygood.api.course.dto.CourseNameResponse; -import org.wooriverygood.api.course.dto.CourseResponse; -import org.wooriverygood.api.course.dto.NewCourseRequest; -import org.wooriverygood.api.course.dto.NewCourseResponse; -import org.wooriverygood.api.course.service.CourseService; - -import java.util.List; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/courses") -public class CourseController { - private final CourseService courseService; - - @GetMapping - public ResponseEntity> findAllCourses() { - Listresponses = courseService.findAll(); - return ResponseEntity.ok(responses); - } - - @PostMapping - public ResponseEntity addCourse(@Valid @RequestBody NewCourseRequest newCourseRequest) { - NewCourseResponse response = courseService.addCourse(newCourseRequest); - return ResponseEntity.status(HttpStatus.CREATED).body(response); - } - - @GetMapping("/{id}/name") - public ResponseEntity getCourseName(@PathVariable("id") Long courseId) { - CourseNameResponse response = courseService.getCourseName(courseId); - return ResponseEntity.ok(response); - } -} diff --git a/src/main/java/org/wooriverygood/api/course/domain/Course.java b/src/main/java/org/wooriverygood/api/course/domain/Course.java index abaf526..efab51b 100644 --- a/src/main/java/org/wooriverygood/api/course/domain/Course.java +++ b/src/main/java/org/wooriverygood/api/course/domain/Course.java @@ -11,19 +11,20 @@ @NoArgsConstructor @Table(name = "courses") public class Course { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "course_id") private Long id; @Column(name = "course_category", nullable = false) - private String course_category; + private String category; @Column(name = "course_credit", nullable = false) - private double course_credit; + private double credit; @Column(name = "course_name", nullable = false) - private String course_name; + private String name; @Column(name = "isYouguan", nullable = false) private int isYouguan; @@ -36,16 +37,14 @@ public class Course { private int reviewCount; @Builder - public Course(Long id, String course_name, String course_category, double course_credit, int isYouguan, String kaikeYuanxi, int reviewCount) { + public Course(Long id, String name, String category, double credit, int isYouguan, String kaikeYuanxi, int reviewCount) { this.id = id; - this.course_name = course_name; - this.course_category = course_category; - this.course_credit = course_credit; + this.name = name; + this.category = category; + this.credit = credit; this.isYouguan = isYouguan; this.kaikeYuanxi = kaikeYuanxi; this.reviewCount = reviewCount; } - - } diff --git a/src/main/java/org/wooriverygood/api/course/dto/CourseNameResponse.java b/src/main/java/org/wooriverygood/api/course/dto/CourseNameResponse.java index 80d1001..efb808d 100644 --- a/src/main/java/org/wooriverygood/api/course/dto/CourseNameResponse.java +++ b/src/main/java/org/wooriverygood/api/course/dto/CourseNameResponse.java @@ -1,14 +1,17 @@ package org.wooriverygood.api.course.dto; -import lombok.Builder; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; import lombok.Getter; @Getter +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) public class CourseNameResponse { - private final String course_name; - @Builder - public CourseNameResponse(String course_name) { - this.course_name = course_name; + private final String courseName; + + public CourseNameResponse(String courseName) { + this.courseName = courseName; } + } diff --git a/src/main/java/org/wooriverygood/api/course/dto/CourseResponse.java b/src/main/java/org/wooriverygood/api/course/dto/CourseResponse.java index 859e97e..cec3cc6 100644 --- a/src/main/java/org/wooriverygood/api/course/dto/CourseResponse.java +++ b/src/main/java/org/wooriverygood/api/course/dto/CourseResponse.java @@ -1,39 +1,51 @@ package org.wooriverygood.api.course.dto; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; import lombok.Builder; import lombok.Getter; import org.wooriverygood.api.course.domain.Course; @Getter +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) public class CourseResponse { - private final Long course_id; - private final String course_category; - private final double course_credit; - private final String course_name; + + private final Long courseId; + + private final String courseCategory; + + private final double courseCredit; + + private final String courseName; + private final int isYouguan; + private final String kaikeYuanxi; + private final int reviewCount; + @Builder - public CourseResponse(Long course_id, String course_category, double course_credit, String course_name, int isYouguan, String kaikeYuanxi, int reviewCount) { - this.course_id = course_id; - this.course_category = course_category; - this.course_credit = course_credit; - this.course_name = course_name; + public CourseResponse(Long courseId, String courseCategory, double courseCredit, String courseName, int isYouguan, String kaikeYuanxi, int reviewCount) { + this.courseId = courseId; + this.courseCategory = courseCategory; + this.courseCredit = courseCredit; + this.courseName = courseName; this.isYouguan = isYouguan; this.kaikeYuanxi = kaikeYuanxi; this.reviewCount = reviewCount; } - public static CourseResponse from(Course course) { + public static CourseResponse of(Course course) { return CourseResponse.builder() - .course_id(course.getId()) - .course_category(course.getCourse_category()) - .course_credit(course.getCourse_credit()) - .course_name(course.getCourse_name()) + .courseId(course.getId()) + .courseCategory(course.getCategory()) + .courseCredit(course.getCredit()) + .courseName(course.getName()) .isYouguan(course.getIsYouguan()) .kaikeYuanxi(course.getKaikeYuanxi()) .reviewCount(course.getReviewCount()) .build(); } + } diff --git a/src/main/java/org/wooriverygood/api/course/dto/CoursesResponse.java b/src/main/java/org/wooriverygood/api/course/dto/CoursesResponse.java new file mode 100644 index 0000000..c1164be --- /dev/null +++ b/src/main/java/org/wooriverygood/api/course/dto/CoursesResponse.java @@ -0,0 +1,17 @@ +package org.wooriverygood.api.course.dto; + +import lombok.Getter; + +import java.util.List; + +@Getter +public class CoursesResponse { + + private final List courses; + + + public CoursesResponse(List courses) { + this.courses = courses; + } + +} diff --git a/src/main/java/org/wooriverygood/api/course/dto/NewCourseRequest.java b/src/main/java/org/wooriverygood/api/course/dto/NewCourseRequest.java index 9706cf3..e86dbae 100644 --- a/src/main/java/org/wooriverygood/api/course/dto/NewCourseRequest.java +++ b/src/main/java/org/wooriverygood/api/course/dto/NewCourseRequest.java @@ -1,22 +1,32 @@ package org.wooriverygood.api.course.dto; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; import lombok.Builder; import lombok.Getter; @Getter +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) public class NewCourseRequest { - private final String course_name; - private final double course_credit; - private final String course_category; + + private final String courseName; + + private final double courseCredit; + + private final String courseCategory; + private final String kaikeYuanxi; + private final int isYouguan; + @Builder - public NewCourseRequest(String course_name, double course_credit, String course_category, String kaikeYuanxi, int isYouguan) { - this.course_name = course_name; - this.course_credit = course_credit; - this.course_category = course_category; + public NewCourseRequest(String courseName, double courseCredit, String courseCategory, String kaikeYuanxi, int isYouguan) { + this.courseName = courseName; + this.courseCredit = courseCredit; + this.courseCategory = courseCategory; this.kaikeYuanxi = kaikeYuanxi; this.isYouguan = isYouguan; } + } diff --git a/src/main/java/org/wooriverygood/api/course/dto/NewCourseResponse.java b/src/main/java/org/wooriverygood/api/course/dto/NewCourseResponse.java deleted file mode 100644 index 5671bcb..0000000 --- a/src/main/java/org/wooriverygood/api/course/dto/NewCourseResponse.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.wooriverygood.api.course.dto; - -import lombok.Builder; -import lombok.Getter; - -@Getter -public class NewCourseResponse { - private final Long course_id; - private final String course_name; - private final double course_credit; - private final String course_category; - private final String kaikeYuanxi; - private final int isYouguan; - - @Builder - public NewCourseResponse(Long course_id, String course_name, double course_credit, String course_category, String kaikeYuanxi, int isYouguan) { - this.course_id = course_id; - this.course_name = course_name; - this.course_credit = course_credit; - this.course_category = course_category; - this.kaikeYuanxi = kaikeYuanxi; - this.isYouguan = isYouguan; - } -} diff --git a/src/main/java/org/wooriverygood/api/advice/exception/CourseNotFoundException.java b/src/main/java/org/wooriverygood/api/course/exception/CourseNotFoundException.java similarity index 85% rename from src/main/java/org/wooriverygood/api/advice/exception/CourseNotFoundException.java rename to src/main/java/org/wooriverygood/api/course/exception/CourseNotFoundException.java index ff57ce2..9ef3403 100644 --- a/src/main/java/org/wooriverygood/api/advice/exception/CourseNotFoundException.java +++ b/src/main/java/org/wooriverygood/api/course/exception/CourseNotFoundException.java @@ -1,4 +1,4 @@ -package org.wooriverygood.api.advice.exception; +package org.wooriverygood.api.course.exception; import org.wooriverygood.api.global.error.exception.NotFoundException; diff --git a/src/main/java/org/wooriverygood/api/course/service/CourseService.java b/src/main/java/org/wooriverygood/api/course/service/CourseService.java deleted file mode 100644 index 7753192..0000000 --- a/src/main/java/org/wooriverygood/api/course/service/CourseService.java +++ /dev/null @@ -1,64 +0,0 @@ -package org.wooriverygood.api.course.service; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.wooriverygood.api.advice.exception.CourseNotFoundException; -import org.wooriverygood.api.course.domain.Course; -import org.wooriverygood.api.course.dto.CourseNameResponse; -import org.wooriverygood.api.course.dto.CourseResponse; -import org.wooriverygood.api.course.dto.NewCourseRequest; -import org.wooriverygood.api.course.dto.NewCourseResponse; -import org.wooriverygood.api.course.repository.CourseRepository; - -import java.util.List; - -@RequiredArgsConstructor -@Service -@Transactional(readOnly = true) -public class CourseService { - private final CourseRepository courseRepository; - - public List findAll() { - return courseRepository.findAll().stream().map(CourseResponse::from).toList(); - } - - @Transactional - public NewCourseResponse addCourse(NewCourseRequest newCourseRequest) { - Course course = createCourse(newCourseRequest); - Course saved = courseRepository.save(course); - return createResponse(saved); - } - - public CourseNameResponse getCourseName(Long courseId) { - Course courses = courseRepository.findById(courseId) - .orElseThrow(CourseNotFoundException::new); - - return CourseNameResponse.builder() - .course_name(courses.getCourse_name()) - .build(); - - } - - private Course createCourse(NewCourseRequest newCourseRequest) { - return Course.builder() - .course_name(newCourseRequest.getCourse_name()) - .course_category(newCourseRequest.getCourse_category()) - .course_credit(newCourseRequest.getCourse_credit()) - .isYouguan(newCourseRequest.getIsYouguan()) - .kaikeYuanxi(newCourseRequest.getKaikeYuanxi()) - .build(); - } - - private NewCourseResponse createResponse(Course course) { - return NewCourseResponse.builder() - .course_id(course.getId()) - .course_name(course.getCourse_name()) - .course_category(course.getCourse_category()) - .course_credit(course.getCourse_credit()) - .isYouguan(course.getIsYouguan()) - .kaikeYuanxi(course.getKaikeYuanxi()) - .build(); - } - -} diff --git a/src/main/java/org/wooriverygood/api/review/application/ReviewCreateService.java b/src/main/java/org/wooriverygood/api/review/application/ReviewCreateService.java index 4a98851..2804038 100644 --- a/src/main/java/org/wooriverygood/api/review/application/ReviewCreateService.java +++ b/src/main/java/org/wooriverygood/api/review/application/ReviewCreateService.java @@ -3,7 +3,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.wooriverygood.api.advice.exception.CourseNotFoundException; +import org.wooriverygood.api.course.exception.CourseNotFoundException; import org.wooriverygood.api.course.domain.Course; import org.wooriverygood.api.course.repository.CourseRepository; import org.wooriverygood.api.global.auth.AuthInfo; diff --git a/src/test/java/org/wooriverygood/api/course/controller/CourseApiTest.java b/src/test/java/org/wooriverygood/api/course/api/CourseApiTest.java similarity index 75% rename from src/test/java/org/wooriverygood/api/course/controller/CourseApiTest.java rename to src/test/java/org/wooriverygood/api/course/api/CourseApiTest.java index 4edc3d4..310b165 100644 --- a/src/test/java/org/wooriverygood/api/course/controller/CourseApiTest.java +++ b/src/test/java/org/wooriverygood/api/course/api/CourseApiTest.java @@ -1,33 +1,35 @@ -package org.wooriverygood.api.course.controller; +package org.wooriverygood.api.course.api; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.wooriverygood.api.course.dto.CourseNameResponse; import org.wooriverygood.api.course.dto.CourseResponse; +import org.wooriverygood.api.course.dto.CoursesResponse; import org.wooriverygood.api.course.dto.NewCourseRequest; import org.wooriverygood.api.util.ApiTest; import java.util.ArrayList; import java.util.List; -import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.when; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; public class CourseApiTest extends ApiTest { - List responses = new ArrayList<>(); + + private List responses = new ArrayList<>(); @BeforeEach void setUp() { - for (int i = 0; i < 2; i++) { + for (long i = 0; i < 2; i++) { responses.add(CourseResponse.builder() - .course_id((long)i) - .course_category("Zhuanye") - .course_credit(5) - .course_name("Gaoshu"+i) + .courseId(i) + .courseCategory("Zhuanye") + .courseCredit(5) + .courseName("Gaoshu"+i) .isYouguan(0) .kaikeYuanxi("Xinke") .reviewCount(0) @@ -38,7 +40,7 @@ void setUp() { @Test @DisplayName("모든 강의 조회 요청을 받으면 강의를 반환한다.") void getCourses() { - Mockito.when(courseService.findAll()).thenReturn(responses); + when(courseFindService.findAll()).thenReturn(new CoursesResponse(responses)); restDocs .contentType(MediaType.APPLICATION_JSON_VALUE) @@ -53,9 +55,9 @@ void getCourses() { @DisplayName("새로운 수업을 성공적으로 등록한다.") public void addCourse() { NewCourseRequest request = NewCourseRequest.builder() - .course_name("테스트 강의") - .course_category("전공") - .course_credit(5) + .courseName("테스트 강의") + .courseCategory("전공") + .courseCredit(5) .kaikeYuanxi("씬커") .isYouguan(0) .build(); @@ -73,11 +75,9 @@ public void addCourse() { @Test @DisplayName("특정 강의의 이름을 반환한다.") void getCourseName() { - CourseNameResponse response = CourseNameResponse.builder() - .course_name("테스트 강의") - .build(); + CourseNameResponse response = new CourseNameResponse("테스트 강의"); - Mockito.when(courseService.getCourseName(any(Long.class))) + when(courseFindService.findCourseName(anyLong())) .thenReturn(response); restDocs @@ -89,4 +89,5 @@ void getCourseName() { .apply(document("course/get/name/success")) .statusCode(HttpStatus.OK.value()); } + } diff --git a/src/test/java/org/wooriverygood/api/course/application/CourseCreateServiceTest.java b/src/test/java/org/wooriverygood/api/course/application/CourseCreateServiceTest.java new file mode 100644 index 0000000..f8c0b2e --- /dev/null +++ b/src/test/java/org/wooriverygood/api/course/application/CourseCreateServiceTest.java @@ -0,0 +1,40 @@ +package org.wooriverygood.api.course.application; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.wooriverygood.api.course.domain.Course; +import org.wooriverygood.api.course.dto.NewCourseRequest; +import org.wooriverygood.api.course.repository.CourseRepository; +import org.wooriverygood.api.util.MockTest; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; + +public class CourseCreateServiceTest extends MockTest { + + @InjectMocks + private CourseCreateService courseCreateService; + + @Mock + private CourseRepository courseRepository; + + + @Test + @DisplayName("새로운 강의를 추가한다.") + void addCourse() { + NewCourseRequest request = NewCourseRequest.builder() + .courseName("테스트 강의") + .courseCategory("전공") + .courseCredit(5) + .kaikeYuanxi("씬커") + .isYouguan(0) + .build(); + + courseCreateService.addCourse(request); + + verify(courseRepository).save(any(Course.class)); + } + +} diff --git a/src/test/java/org/wooriverygood/api/course/application/CourseFindServiceTest.java b/src/test/java/org/wooriverygood/api/course/application/CourseFindServiceTest.java new file mode 100644 index 0000000..625fc07 --- /dev/null +++ b/src/test/java/org/wooriverygood/api/course/application/CourseFindServiceTest.java @@ -0,0 +1,81 @@ +package org.wooriverygood.api.course.application; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.wooriverygood.api.course.domain.Course; +import org.wooriverygood.api.course.dto.CourseNameResponse; +import org.wooriverygood.api.course.dto.CoursesResponse; +import org.wooriverygood.api.course.repository.CourseRepository; +import org.wooriverygood.api.util.MockTest; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.when; + +class CourseFindServiceTest extends MockTest { + + @InjectMocks + private CourseFindService courseFindService; + + @Mock + private CourseRepository courseRepository; + + private Course course = Course.builder() + .id(3L) + .name("Gaoshu Test") + .category("Zhuanye") + .credit(5) + .isYouguan(0) + .kaikeYuanxi("Xinke") + .reviewCount(0) + .build(); + + private List courses = new ArrayList<>(); + + private final int COURSES_COUNT = 10; + + + @BeforeEach + void setup() { + for (long i = 1; i <= COURSES_COUNT; i++) { + courses.add(Course.builder() + .id(i) + .name("Course" + i) + .category("Zhuanye") + .credit(5) + .isYouguan(0) + .kaikeYuanxi("Xinke") + .reviewCount(0) + .build()); + } + } + + @Test + @DisplayName("강의 전체 조회") + void getCourses() { + when(courseRepository.findAll()).thenReturn(courses); + + CoursesResponse response = courseFindService.findAll(); + + Assertions.assertThat(response.getCourses().size()).isEqualTo(COURSES_COUNT); + } + + @Test + @DisplayName("특정 강의의 이름을 조회온다.") + void getCourseName() { + when(courseRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(course)); + + CourseNameResponse response = courseFindService.findCourseName(3L); + + Assertions.assertThat(response.getCourseName()).isEqualTo(course.getName()); + } + +} \ No newline at end of file diff --git a/src/test/java/org/wooriverygood/api/course/service/CourseServiceTest.java b/src/test/java/org/wooriverygood/api/course/service/CourseServiceTest.java deleted file mode 100644 index b4fd260..0000000 --- a/src/test/java/org/wooriverygood/api/course/service/CourseServiceTest.java +++ /dev/null @@ -1,116 +0,0 @@ -package org.wooriverygood.api.course.service; - -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -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.Mockito; -import org.mockito.junit.jupiter.MockitoExtension; -import org.wooriverygood.api.course.domain.Course; -import org.wooriverygood.api.course.dto.CourseNameResponse; -import org.wooriverygood.api.course.dto.CourseResponse; -import org.wooriverygood.api.course.dto.NewCourseRequest; -import org.wooriverygood.api.course.dto.NewCourseResponse; -import org.wooriverygood.api.course.repository.CourseRepository; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import static org.mockito.ArgumentMatchers.any; - -@ExtendWith(MockitoExtension.class) -public class CourseServiceTest { - @Mock - private CourseRepository courseRepository; - @InjectMocks - private CourseService courseService; - - private List testCourses = new ArrayList<>(); - - private final int COURSES_COUNT = 10; - - @BeforeEach - void setup() { - for(int i = 1; i<=COURSES_COUNT; i++) { - testCourses.add(Course.builder() - .id((long)i) - .course_name("Gaoshu" + i) - .course_category("Zhuanye") - .course_credit(5) - .isYouguan(0) - .kaikeYuanxi("Xinke") - .reviewCount(0) - .build()); - } - } - - Course singleCourse = Course.builder() - .id(3L) - .course_name("Gaoshu Test") - .course_category("Zhuanye") - .course_credit(5) - .isYouguan(0) - .kaikeYuanxi("Xinke") - .reviewCount(0) - .build(); - - - @Test - @DisplayName("강의 목록 조회") - void getCourses() { - Mockito.when(courseRepository.findAll()).thenReturn(testCourses); - - List responses = courseService.findAll(); - - // Then - Assertions.assertThat(responses.size()).isEqualTo(COURSES_COUNT); - - } - - @Test - @DisplayName("새로운 강의를 추가한다.") - void addCourse() { - NewCourseRequest newCourseRequest = NewCourseRequest.builder() - .course_name("테스트 강의") - .course_category("전공") - .course_credit(5) - .kaikeYuanxi("씬커") - .isYouguan(0) - .build(); - - Mockito.when(courseRepository.save(any(Course.class))) - .thenReturn(Course.builder() - .course_name(newCourseRequest.getCourse_name()) - .course_category(newCourseRequest.getCourse_category()) - .course_credit(newCourseRequest.getCourse_credit()) - .kaikeYuanxi(newCourseRequest.getKaikeYuanxi()) - .isYouguan(newCourseRequest.getIsYouguan()) - .reviewCount(0) - .build()); - - NewCourseResponse response = courseService.addCourse(newCourseRequest); - - Assertions.assertThat(response.getCourse_name()).isEqualTo(newCourseRequest.getCourse_name()); - Assertions.assertThat(response.getCourse_credit()).isEqualTo(newCourseRequest.getCourse_credit()); - Assertions.assertThat(response.getCourse_category()).isEqualTo(newCourseRequest.getCourse_category()); - Assertions.assertThat(response.getKaikeYuanxi()).isEqualTo(newCourseRequest.getKaikeYuanxi()); - Assertions.assertThat(response.getIsYouguan()).isEqualTo(newCourseRequest.getIsYouguan()); - } - - @Test - @DisplayName("특정 강의의 이름을 받아온다.") - void getCourseName() { - Mockito.when(courseRepository.findById(any())) - .thenReturn(Optional.ofNullable(singleCourse)); - - CourseNameResponse response = courseService.getCourseName(3L); - - Assertions.assertThat(response.getCourse_name()).isEqualTo(singleCourse.getCourse_name()); - - } - -} diff --git a/src/test/java/org/wooriverygood/api/post/api/PostApiTest.java b/src/test/java/org/wooriverygood/api/post/api/PostApiTest.java index 89f5f84..82a0f4c 100644 --- a/src/test/java/org/wooriverygood/api/post/api/PostApiTest.java +++ b/src/test/java/org/wooriverygood/api/post/api/PostApiTest.java @@ -126,8 +126,6 @@ public void addPost() { .postContent("content") .build(); - doNothing().when(postCreateService).addPost(any(AuthInfo.class), any(NewPostRequest.class)); - restDocs .contentType(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer aws-cognito-access-token") @@ -201,10 +199,6 @@ void updatePost() { .postContent("new content") .build(); - doNothing() - .when(postUpdateService) - .updatePost(anyLong(), any(PostUpdateRequest.class), any(AuthInfo.class)); - restDocs .contentType(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer aws-cognito-access-token") @@ -260,8 +254,6 @@ void updatePost_exception_noTitle() { @Test @DisplayName("권한이 있는 게시글을 삭제한다.") void deletePost() { - doNothing().when(postDeleteService).deletePost(any(AuthInfo.class), anyLong()); - restDocs .header("Authorization", "any") .when().delete("/posts/7") diff --git a/src/test/java/org/wooriverygood/api/review/api/ReviewApiTest.java b/src/test/java/org/wooriverygood/api/review/api/ReviewApiTest.java index 087950e..45ad5bf 100644 --- a/src/test/java/org/wooriverygood/api/review/api/ReviewApiTest.java +++ b/src/test/java/org/wooriverygood/api/review/api/ReviewApiTest.java @@ -27,9 +27,9 @@ public class ReviewApiTest extends ApiTest { private Course course = Course.builder() .id(1L) - .course_name("Gaoshu") - .course_category("Zhuanye") - .course_credit(5) + .name("Gaoshu") + .category("Zhuanye") + .credit(5) .isYouguan(0) .kaikeYuanxi("Xinke") .build(); @@ -103,10 +103,6 @@ void addReview() { .grade("100") .build(); - doNothing() - .when(reviewCreateService) - .addReview(any(AuthInfo.class), anyLong(), any(NewReviewRequest.class)); - restDocs .contentType(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer aws-cognito-access-token") @@ -180,10 +176,6 @@ void updateReview() { .grade("100") .build(); - doNothing() - .when(reviewUpdateService) - .updateReview(anyLong(), any(ReviewUpdateRequest.class), any(AuthInfo.class)); - restDocs .contentType(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer aws-cognito-access-token") @@ -221,10 +213,6 @@ void updateReview_noAuth() { @Test @DisplayName("권한이 있는 리뷰를 삭제한다.") void deleteReview() { - doNothing() - .when(reviewDeleteService) - .deleteReview(anyLong(), any(AuthInfo.class)); - restDocs .header("Authorization", "any") .when().delete("/reviews/8") diff --git a/src/test/java/org/wooriverygood/api/review/application/ReviewCreateServiceTest.java b/src/test/java/org/wooriverygood/api/review/application/ReviewCreateServiceTest.java index 6b555ba..dda08e1 100644 --- a/src/test/java/org/wooriverygood/api/review/application/ReviewCreateServiceTest.java +++ b/src/test/java/org/wooriverygood/api/review/application/ReviewCreateServiceTest.java @@ -36,9 +36,9 @@ class ReviewCreateServiceTest extends MockTest { private Course course = Course.builder() .id(1L) - .course_name("Gaoshu") - .course_category("Zhuanye") - .course_credit(5) + .name("Gaoshu") + .category("Zhuanye") + .credit(5) .isYouguan(0) .kaikeYuanxi("Xinke") .build(); diff --git a/src/test/java/org/wooriverygood/api/review/application/ReviewFindServiceTest.java b/src/test/java/org/wooriverygood/api/review/application/ReviewFindServiceTest.java index 89c8ad0..29677fb 100644 --- a/src/test/java/org/wooriverygood/api/review/application/ReviewFindServiceTest.java +++ b/src/test/java/org/wooriverygood/api/review/application/ReviewFindServiceTest.java @@ -41,9 +41,9 @@ class ReviewFindServiceTest extends MockTest { private Course course = Course.builder() .id(1L) - .course_name("Gaoshu") - .course_category("Zhuanye") - .course_credit(5) + .name("Gaoshu") + .category("Zhuanye") + .credit(5) .isYouguan(0) .kaikeYuanxi("Xinke") .build(); diff --git a/src/test/java/org/wooriverygood/api/util/ApiTest.java b/src/test/java/org/wooriverygood/api/util/ApiTest.java index 1dbe89f..cc8a9ff 100644 --- a/src/test/java/org/wooriverygood/api/util/ApiTest.java +++ b/src/test/java/org/wooriverygood/api/util/ApiTest.java @@ -13,11 +13,12 @@ import org.springframework.web.context.WebApplicationContext; import org.wooriverygood.api.comment.controller.CommentController; import org.wooriverygood.api.comment.service.CommentService; +import org.wooriverygood.api.course.application.CourseFindService; import org.wooriverygood.api.post.application.*; import org.wooriverygood.api.report.controller.ReportController; import org.wooriverygood.api.report.service.ReportService; -import org.wooriverygood.api.course.controller.CourseController; -import org.wooriverygood.api.course.service.CourseService; +import org.wooriverygood.api.course.api.CourseApi; +import org.wooriverygood.api.course.application.CourseCreateService; import org.wooriverygood.api.post.api.PostApi; import org.wooriverygood.api.review.api.ReviewApi; import org.wooriverygood.api.review.application.*; @@ -34,7 +35,7 @@ @WebMvcTest({ CommentController.class, PostApi.class, - CourseController.class, + CourseApi.class, ReviewApi.class, ReportController.class }) @@ -45,7 +46,10 @@ public class ApiTest { protected MockMvcRequestSpecification restDocs; @MockBean - protected CourseService courseService; + protected CourseCreateService courseCreateService; + + @MockBean + protected CourseFindService courseFindService; @MockBean protected ReviewLikeToggleService reviewLikeToggleService; From f69fd60f3204fd54f55662e4d247dabe274df0ba Mon Sep 17 00:00:00 2001 From: Park Geonho Date: Tue, 2 Apr 2024 16:40:30 +0800 Subject: [PATCH 13/21] =?UTF-8?q?[refactor]=20=EC=8B=A0=EA=B3=A0=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/post/dto/PostUpdateRequest.java | 4 +- .../ReportApi.java} | 20 +- .../application/CommentReportService.java | 46 +++++ .../report/application/PostReportService.java | 47 +++++ .../api/report/domain/PostReport.java | 3 +- .../api/report/dto/ReportRequest.java | 3 +- .../DuplicatedCommentReportException.java | 2 +- .../DuplicatedPostReportException.java | 2 +- .../api/report/service/ReportService.java | 83 --------- .../{controller => api}/ReportApiTest.java | 21 +-- .../application/CommentReportServiceTest.java | 86 +++++++++ .../application/PostReportServiceTest.java | 86 +++++++++ .../api/report/service/ReportServiceTest.java | 172 ------------------ .../org/wooriverygood/api/util/ApiTest.java | 12 +- .../api/util/ResponseFixture.java | 14 -- 15 files changed, 294 insertions(+), 307 deletions(-) rename src/main/java/org/wooriverygood/api/report/{controller/ReportController.java => api/ReportApi.java} (70%) create mode 100644 src/main/java/org/wooriverygood/api/report/application/CommentReportService.java create mode 100644 src/main/java/org/wooriverygood/api/report/application/PostReportService.java rename src/main/java/org/wooriverygood/api/{advice => report}/exception/DuplicatedCommentReportException.java (86%) rename src/main/java/org/wooriverygood/api/{advice => report}/exception/DuplicatedPostReportException.java (86%) delete mode 100644 src/main/java/org/wooriverygood/api/report/service/ReportService.java rename src/test/java/org/wooriverygood/api/report/{controller => api}/ReportApiTest.java (67%) create mode 100644 src/test/java/org/wooriverygood/api/report/application/CommentReportServiceTest.java create mode 100644 src/test/java/org/wooriverygood/api/report/application/PostReportServiceTest.java delete mode 100644 src/test/java/org/wooriverygood/api/report/service/ReportServiceTest.java diff --git a/src/main/java/org/wooriverygood/api/post/dto/PostUpdateRequest.java b/src/main/java/org/wooriverygood/api/post/dto/PostUpdateRequest.java index d1a09ac..e0c8865 100644 --- a/src/main/java/org/wooriverygood/api/post/dto/PostUpdateRequest.java +++ b/src/main/java/org/wooriverygood/api/post/dto/PostUpdateRequest.java @@ -5,10 +5,8 @@ import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; -import lombok.NoArgsConstructor; @Getter -@NoArgsConstructor @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) public class PostUpdateRequest { @@ -17,9 +15,11 @@ public class PostUpdateRequest { private String postContent; + @Builder public PostUpdateRequest(String postTitle, String postContent) { this.postTitle = postTitle; this.postContent = postContent; } + } diff --git a/src/main/java/org/wooriverygood/api/report/controller/ReportController.java b/src/main/java/org/wooriverygood/api/report/api/ReportApi.java similarity index 70% rename from src/main/java/org/wooriverygood/api/report/controller/ReportController.java rename to src/main/java/org/wooriverygood/api/report/api/ReportApi.java index a97d5ca..4ad5b14 100644 --- a/src/main/java/org/wooriverygood/api/report/controller/ReportController.java +++ b/src/main/java/org/wooriverygood/api/report/api/ReportApi.java @@ -1,33 +1,33 @@ -package org.wooriverygood.api.report.controller; +package org.wooriverygood.api.report.api; import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; +import org.wooriverygood.api.report.application.PostReportService; import org.wooriverygood.api.report.dto.ReportRequest; -import org.wooriverygood.api.report.service.ReportService; +import org.wooriverygood.api.report.application.CommentReportService; import org.wooriverygood.api.global.auth.AuthInfo; import org.wooriverygood.api.global.auth.Login; @RestController -public class ReportController { +@RequiredArgsConstructor +public class ReportApi { - private final ReportService reportService; + private final CommentReportService commentReportService; - - public ReportController(ReportService reportService) { - this.reportService = reportService; - } + private final PostReportService postReportService; @PostMapping("/posts/{id}/report") public ResponseEntity reportPost(@PathVariable("id") Long postId, @Valid @RequestBody ReportRequest request, @Login AuthInfo authInfo) { - reportService.reportPost(postId, request, authInfo); + postReportService.reportPost(postId, request, authInfo); return ResponseEntity.status(HttpStatus.CREATED).build(); } @@ -35,7 +35,7 @@ public ResponseEntity reportPost(@PathVariable("id") Long postId, public ResponseEntity reportComment(@PathVariable("id") Long commentId, @Valid @RequestBody ReportRequest request, @Login AuthInfo authInfo) { - reportService.reportComment(commentId, request, authInfo); + commentReportService.reportComment(commentId, request, authInfo); return ResponseEntity.status(HttpStatus.CREATED).build(); } } diff --git a/src/main/java/org/wooriverygood/api/report/application/CommentReportService.java b/src/main/java/org/wooriverygood/api/report/application/CommentReportService.java new file mode 100644 index 0000000..4a2cf5b --- /dev/null +++ b/src/main/java/org/wooriverygood/api/report/application/CommentReportService.java @@ -0,0 +1,46 @@ +package org.wooriverygood.api.report.application; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.wooriverygood.api.advice.exception.CommentNotFoundException; +import org.wooriverygood.api.report.exception.DuplicatedCommentReportException; +import org.wooriverygood.api.comment.domain.Comment; +import org.wooriverygood.api.comment.repository.CommentRepository; +import org.wooriverygood.api.report.domain.CommentReport; +import org.wooriverygood.api.report.dto.ReportRequest; +import org.wooriverygood.api.report.repository.CommentReportRepository; +import org.wooriverygood.api.global.auth.AuthInfo; + +@Service +@Transactional +@RequiredArgsConstructor +public class CommentReportService { + + private final CommentRepository commentRepository; + + private final CommentReportRepository commentReportRepository; + + + public void reportComment(Long commentId, ReportRequest request, AuthInfo authInfo) { + Comment comment = commentRepository.findById(commentId) + .orElseThrow(CommentNotFoundException::new); + + CommentReport report = CommentReport.builder() + .comment(comment) + .message(request.getMessage()) + .username(authInfo.getUsername()) + .build(); + + checkIfAlreadyReport(comment, authInfo); + comment.addReport(report); + commentRepository.increaseReportCount(commentId); + + commentReportRepository.save(report); + } + + private void checkIfAlreadyReport(Comment comment, AuthInfo authInfo) { + if (comment.hasReportByUser(authInfo.getUsername())) + throw new DuplicatedCommentReportException(); + } +} diff --git a/src/main/java/org/wooriverygood/api/report/application/PostReportService.java b/src/main/java/org/wooriverygood/api/report/application/PostReportService.java new file mode 100644 index 0000000..082672a --- /dev/null +++ b/src/main/java/org/wooriverygood/api/report/application/PostReportService.java @@ -0,0 +1,47 @@ +package org.wooriverygood.api.report.application; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.wooriverygood.api.report.exception.DuplicatedPostReportException; +import org.wooriverygood.api.global.auth.AuthInfo; +import org.wooriverygood.api.post.domain.Post; +import org.wooriverygood.api.post.exception.PostNotFoundException; +import org.wooriverygood.api.post.repository.PostRepository; +import org.wooriverygood.api.report.domain.PostReport; +import org.wooriverygood.api.report.dto.ReportRequest; +import org.wooriverygood.api.report.repository.PostReportRepository; + +@Service +@Transactional +@RequiredArgsConstructor +public class PostReportService { + + private final PostRepository postRepository; + + private final PostReportRepository postReportRepository; + + + public void reportPost(Long postId, ReportRequest request, AuthInfo authInfo) { + Post post = postRepository.findById(postId) + .orElseThrow(PostNotFoundException::new); + + PostReport report = PostReport.builder() + .post(post) + .message(request.getMessage()) + .username(authInfo.getUsername()) + .build(); + + checkIfAlreadyReport(post, authInfo); + post.addReport(report); + postRepository.increaseReportCount(postId); + + postReportRepository.save(report); + } + + private void checkIfAlreadyReport(Post post, AuthInfo authInfo) { + if (post.hasReportByUser(authInfo.getUsername())) + throw new DuplicatedPostReportException(); + } + +} diff --git a/src/main/java/org/wooriverygood/api/report/domain/PostReport.java b/src/main/java/org/wooriverygood/api/report/domain/PostReport.java index 77197b9..352684a 100644 --- a/src/main/java/org/wooriverygood/api/report/domain/PostReport.java +++ b/src/main/java/org/wooriverygood/api/report/domain/PostReport.java @@ -1,6 +1,7 @@ package org.wooriverygood.api.report.domain; import jakarta.persistence.*; +import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -8,7 +9,7 @@ @Entity @Getter -@NoArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class PostReport { @Id diff --git a/src/main/java/org/wooriverygood/api/report/dto/ReportRequest.java b/src/main/java/org/wooriverygood/api/report/dto/ReportRequest.java index ec447b2..b95509a 100644 --- a/src/main/java/org/wooriverygood/api/report/dto/ReportRequest.java +++ b/src/main/java/org/wooriverygood/api/report/dto/ReportRequest.java @@ -1,7 +1,6 @@ package org.wooriverygood.api.report.dto; import jakarta.validation.constraints.NotBlank; -import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -12,8 +11,8 @@ public class ReportRequest { @NotBlank(message = "신고내용은 1자 이상 255자 이하여야 합니다.") private String message; - @Builder public ReportRequest(String message) { this.message = message; } + } diff --git a/src/main/java/org/wooriverygood/api/advice/exception/DuplicatedCommentReportException.java b/src/main/java/org/wooriverygood/api/report/exception/DuplicatedCommentReportException.java similarity index 86% rename from src/main/java/org/wooriverygood/api/advice/exception/DuplicatedCommentReportException.java rename to src/main/java/org/wooriverygood/api/report/exception/DuplicatedCommentReportException.java index 0c4dfa3..bcd3acc 100644 --- a/src/main/java/org/wooriverygood/api/advice/exception/DuplicatedCommentReportException.java +++ b/src/main/java/org/wooriverygood/api/report/exception/DuplicatedCommentReportException.java @@ -1,4 +1,4 @@ -package org.wooriverygood.api.advice.exception; +package org.wooriverygood.api.report.exception; import org.wooriverygood.api.global.error.exception.BadRequestException; diff --git a/src/main/java/org/wooriverygood/api/advice/exception/DuplicatedPostReportException.java b/src/main/java/org/wooriverygood/api/report/exception/DuplicatedPostReportException.java similarity index 86% rename from src/main/java/org/wooriverygood/api/advice/exception/DuplicatedPostReportException.java rename to src/main/java/org/wooriverygood/api/report/exception/DuplicatedPostReportException.java index ef1ead9..3ec45d8 100644 --- a/src/main/java/org/wooriverygood/api/advice/exception/DuplicatedPostReportException.java +++ b/src/main/java/org/wooriverygood/api/report/exception/DuplicatedPostReportException.java @@ -1,4 +1,4 @@ -package org.wooriverygood.api.advice.exception; +package org.wooriverygood.api.report.exception; import org.wooriverygood.api.global.error.exception.BadRequestException; diff --git a/src/main/java/org/wooriverygood/api/report/service/ReportService.java b/src/main/java/org/wooriverygood/api/report/service/ReportService.java deleted file mode 100644 index 4207f02..0000000 --- a/src/main/java/org/wooriverygood/api/report/service/ReportService.java +++ /dev/null @@ -1,83 +0,0 @@ -package org.wooriverygood.api.report.service; - -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.wooriverygood.api.advice.exception.CommentNotFoundException; -import org.wooriverygood.api.advice.exception.DuplicatedCommentReportException; -import org.wooriverygood.api.advice.exception.DuplicatedPostReportException; -import org.wooriverygood.api.post.exception.PostNotFoundException; -import org.wooriverygood.api.comment.domain.Comment; -import org.wooriverygood.api.comment.repository.CommentRepository; -import org.wooriverygood.api.post.domain.Post; -import org.wooriverygood.api.post.repository.PostRepository; -import org.wooriverygood.api.report.domain.CommentReport; -import org.wooriverygood.api.report.domain.PostReport; -import org.wooriverygood.api.report.dto.ReportRequest; -import org.wooriverygood.api.report.repository.CommentReportRepository; -import org.wooriverygood.api.report.repository.PostReportRepository; -import org.wooriverygood.api.global.auth.AuthInfo; - -@Service -@Transactional -public class ReportService { - - private final CommentRepository commentRepository; - - private final CommentReportRepository commentReportRepository; - - private final PostRepository postRepository; - - private final PostReportRepository postReportRepository; - - - public ReportService(CommentRepository commentRepository, CommentReportRepository commentReportRepository, PostRepository postRepository, PostReportRepository postReportRepository) { - this.commentRepository = commentRepository; - this.commentReportRepository = commentReportRepository; - this.postRepository = postRepository; - this.postReportRepository = postReportRepository; - } - - public void reportPost(Long postId, ReportRequest request, AuthInfo authInfo) { - Post post = postRepository.findById(postId) - .orElseThrow(PostNotFoundException::new); - - PostReport report = PostReport.builder() - .post(post) - .message(request.getMessage()) - .username(authInfo.getUsername()) - .build(); - - checkAlreadyReport(post, authInfo); - post.addReport(report); - postRepository.increaseReportCount(postId); - - postReportRepository.save(report); - } - - private void checkAlreadyReport(Post post, AuthInfo authInfo) { - if (post.hasReportByUser(authInfo.getUsername())) - throw new DuplicatedPostReportException(); - } - - public void reportComment(Long commentId, ReportRequest request, AuthInfo authInfo) { - Comment comment = commentRepository.findById(commentId) - .orElseThrow(CommentNotFoundException::new); - - CommentReport report = CommentReport.builder() - .comment(comment) - .message(request.getMessage()) - .username(authInfo.getUsername()) - .build(); - - checkAlreadyReport(comment, authInfo); - comment.addReport(report); - commentRepository.increaseReportCount(commentId); - - commentReportRepository.save(report); - } - - private void checkAlreadyReport(Comment comment, AuthInfo authInfo) { - if (comment.hasReportByUser(authInfo.getUsername())) - throw new DuplicatedCommentReportException(); - } -} diff --git a/src/test/java/org/wooriverygood/api/report/controller/ReportApiTest.java b/src/test/java/org/wooriverygood/api/report/api/ReportApiTest.java similarity index 67% rename from src/test/java/org/wooriverygood/api/report/controller/ReportApiTest.java rename to src/test/java/org/wooriverygood/api/report/api/ReportApiTest.java index 5f02f77..9e1067d 100644 --- a/src/test/java/org/wooriverygood/api/report/controller/ReportApiTest.java +++ b/src/test/java/org/wooriverygood/api/report/api/ReportApiTest.java @@ -1,15 +1,12 @@ -package org.wooriverygood.api.report.controller; +package org.wooriverygood.api.report.api; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.wooriverygood.api.report.dto.ReportRequest; -import org.wooriverygood.api.global.auth.AuthInfo; import org.wooriverygood.api.util.ApiTest; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doNothing; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; public class ReportApiTest extends ApiTest { @@ -17,12 +14,7 @@ public class ReportApiTest extends ApiTest { @Test @DisplayName("특정 게시글을 신고하면 201을 반환한다.") void reportPost() { - ReportRequest request = ReportRequest.builder() - .message("신고 내용") - .build(); - - doNothing().when(reportService) - .reportPost(any(Long.class), any(ReportRequest.class), any(AuthInfo.class)); + ReportRequest request = new ReportRequest("신고 내용"); restDocs .contentType(MediaType.APPLICATION_JSON_VALUE) @@ -38,12 +30,7 @@ void reportPost() { @Test @DisplayName("특정 댓글을 신고하면 201을 반환한다.") void reportComment() { - ReportRequest request = ReportRequest.builder() - .message("신고 내용") - .build(); - - doNothing().when(reportService) - .reportComment(any(Long.class), any(ReportRequest.class), any(AuthInfo.class)); + ReportRequest request = new ReportRequest("신고 내용"); restDocs .contentType(MediaType.APPLICATION_JSON_VALUE) @@ -55,5 +42,5 @@ void reportComment() { .apply(document("comments/report/success")) .statusCode(HttpStatus.CREATED.value()); } -} +} diff --git a/src/test/java/org/wooriverygood/api/report/application/CommentReportServiceTest.java b/src/test/java/org/wooriverygood/api/report/application/CommentReportServiceTest.java new file mode 100644 index 0000000..5dae872 --- /dev/null +++ b/src/test/java/org/wooriverygood/api/report/application/CommentReportServiceTest.java @@ -0,0 +1,86 @@ +package org.wooriverygood.api.report.application; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.wooriverygood.api.advice.exception.CommentNotFoundException; +import org.wooriverygood.api.report.domain.CommentReport; +import org.wooriverygood.api.report.exception.DuplicatedCommentReportException; +import org.wooriverygood.api.comment.domain.Comment; +import org.wooriverygood.api.comment.repository.CommentRepository; +import org.wooriverygood.api.report.dto.ReportRequest; +import org.wooriverygood.api.report.repository.CommentReportRepository; +import org.wooriverygood.api.global.auth.AuthInfo; +import org.wooriverygood.api.util.MockTest; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class CommentReportServiceTest extends MockTest { + + @InjectMocks + private CommentReportService commentReportService; + + @Mock + private CommentRepository commentRepository; + + @Mock + private CommentReportRepository commentReportRepository; + + private AuthInfo authInfo = AuthInfo.builder() + .sub("22432-12312-3531") + .username("22432-12312-3531") + .build(); + + @Mock + private Comment comment; + + + @Test + @DisplayName("유효한 id를 통해 특정 댓글을 신고한다.") + void reportComment() { + ReportRequest request = new ReportRequest("report message"); + when(comment.hasReportByUser(anyString())) + .thenReturn(false); + when(commentRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(comment)); + + commentReportService.reportComment(1L, request, authInfo); + + verify(comment).hasReportByUser(authInfo.getUsername()); + verify(comment).addReport(any(CommentReport.class)); + verify(commentRepository).increaseReportCount(anyLong()); + verify(commentReportRepository).save(any(CommentReport.class)); + } + + @Test + @DisplayName("신고하려는 댓글의 id가 유효하지 않으면 예외를 발생한다.") + void reportComment_exception_invalidId() { + ReportRequest request = new ReportRequest("report message"); + + when(commentRepository.findById(anyLong())) + .thenThrow(new CommentNotFoundException()); + + assertThatThrownBy(() -> commentReportService.reportComment(1L, request, authInfo)) + .isInstanceOf(CommentNotFoundException.class); + } + + @Test + @DisplayName("동일한 댓글을 한 번 이상 신고하면 예외를 발생한다.") + void reportComment_exception_duplicated() { + ReportRequest request = new ReportRequest("report message"); + when(comment.hasReportByUser(anyString())) + .thenReturn(true); + when(commentRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(comment)); + + assertThatThrownBy(() -> commentReportService.reportComment(1L, request, authInfo)) + .isInstanceOf(DuplicatedCommentReportException.class); + } + +} \ No newline at end of file diff --git a/src/test/java/org/wooriverygood/api/report/application/PostReportServiceTest.java b/src/test/java/org/wooriverygood/api/report/application/PostReportServiceTest.java new file mode 100644 index 0000000..d778050 --- /dev/null +++ b/src/test/java/org/wooriverygood/api/report/application/PostReportServiceTest.java @@ -0,0 +1,86 @@ +package org.wooriverygood.api.report.application; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.wooriverygood.api.global.auth.AuthInfo; +import org.wooriverygood.api.post.domain.Post; +import org.wooriverygood.api.post.exception.PostNotFoundException; +import org.wooriverygood.api.post.repository.PostRepository; +import org.wooriverygood.api.report.domain.PostReport; +import org.wooriverygood.api.report.dto.ReportRequest; +import org.wooriverygood.api.report.exception.DuplicatedPostReportException; +import org.wooriverygood.api.report.repository.PostReportRepository; +import org.wooriverygood.api.util.MockTest; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class PostReportServiceTest extends MockTest { + + @InjectMocks + private PostReportService postReportService; + + @Mock + private PostRepository postRepository; + + @Mock + private PostReportRepository postReportRepository; + + private AuthInfo authInfo = AuthInfo.builder() + .sub("22432-12312-3531") + .username("22432-12312-3531") + .build(); + + @Mock + private Post post; + + + @Test + @DisplayName("유효한 id를 통해 특정 게시글을 신고한다.") + void reportPost() { + ReportRequest request = new ReportRequest("report message"); + when(post.hasReportByUser(anyString())) + .thenReturn(false); + when(postRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(post)); + + postReportService.reportPost(1L, request, authInfo); + + verify(post).hasReportByUser(authInfo.getUsername()); + verify(post).addReport(any(PostReport.class)); + verify(postRepository).increaseReportCount(anyLong()); + verify(postReportRepository).save(any(PostReport.class)); + } + + @Test + @DisplayName("신고하려는 게시글 id가 유효하지 않으면 예외를 발생한다.") + void reportPost_exception_invalidId() { + ReportRequest request = new ReportRequest("report message"); + + when(postRepository.findById(anyLong())) + .thenThrow(new PostNotFoundException()); + + assertThatThrownBy(() -> postReportService.reportPost(1L, request, authInfo)) + .isInstanceOf(PostNotFoundException.class); + } + + @Test + @DisplayName("동일한 게시글을 한 번 이상 신고하면 예외를 발생한다.") + void reportPost_exception_duplicated() { + ReportRequest request = new ReportRequest("report message"); + when(post.hasReportByUser(anyString())) + .thenReturn(true); + when(postRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(post)); + + assertThatThrownBy(() -> postReportService.reportPost(1L, request, authInfo)) + .isInstanceOf(DuplicatedPostReportException.class); + } + +} \ No newline at end of file diff --git a/src/test/java/org/wooriverygood/api/report/service/ReportServiceTest.java b/src/test/java/org/wooriverygood/api/report/service/ReportServiceTest.java deleted file mode 100644 index 0c53e1e..0000000 --- a/src/test/java/org/wooriverygood/api/report/service/ReportServiceTest.java +++ /dev/null @@ -1,172 +0,0 @@ -package org.wooriverygood.api.report.service; - -import org.junit.jupiter.api.BeforeEach; -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.wooriverygood.api.advice.exception.CommentNotFoundException; -import org.wooriverygood.api.advice.exception.DuplicatedCommentReportException; -import org.wooriverygood.api.advice.exception.DuplicatedPostReportException; -import org.wooriverygood.api.post.exception.PostNotFoundException; -import org.wooriverygood.api.comment.domain.Comment; -import org.wooriverygood.api.comment.repository.CommentRepository; -import org.wooriverygood.api.post.domain.Post; -import org.wooriverygood.api.post.domain.PostCategory; -import org.wooriverygood.api.post.repository.PostRepository; -import org.wooriverygood.api.report.dto.ReportRequest; -import org.wooriverygood.api.report.repository.CommentReportRepository; -import org.wooriverygood.api.report.repository.PostReportRepository; -import org.wooriverygood.api.global.auth.AuthInfo; - -import java.util.ArrayList; -import java.util.Optional; - -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.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class ReportServiceTest { - - @InjectMocks - private ReportService reportService; - - @Mock - private CommentRepository commentRepository; - - @Mock - private CommentReportRepository commentReportRepository; - - @Mock - private PostRepository postRepository; - - @Mock - private PostReportRepository postReportRepository; - - private AuthInfo testAuthInfo; - - private Post testPost; - - private Comment testComment; - - - @BeforeEach - void setUp() { - testAuthInfo = AuthInfo.builder() - .sub("22432-12312-3531") - .username("22432-12312-3531") - .build(); - - testPost = Post.builder() - .id(1L) - .category(PostCategory.OFFER) - .title("post title") - .content("post content") - .author(testAuthInfo.getUsername()) - .comments(new ArrayList<>()) - .postLikes(new ArrayList<>()) - .reports(new ArrayList<>()) - .build(); - - testComment = Comment.builder() - .id(1L) - .content("comment content") - .author(testAuthInfo.getUsername()) - .commentLikes(new ArrayList<>()) - .reports(new ArrayList<>()) - .build(); - } - - @Test - @DisplayName("유효한 id를 통해 특정 게시글을 신고한다.") - void reportPost() { - ReportRequest request = ReportRequest.builder() - .message("report message") - .build(); - - when(postRepository.findById(any(Long.class))) - .thenReturn(Optional.ofNullable(testPost)); - - reportService.reportPost(1L, request, testAuthInfo); - - assertThat(testPost.getReports().size()).isEqualTo(1); - } - - @Test - @DisplayName("신고하려는 게시글 id가 유효하지 않으면 예외를 발생한다.") - void reportPost_exception_invalidId() { - ReportRequest request = ReportRequest.builder() - .message("report message") - .build(); - - when(postRepository.findById(any(Long.class))) - .thenThrow(new PostNotFoundException()); - - assertThatThrownBy(() -> reportService.reportPost(1L, request, testAuthInfo)) - .isInstanceOf(PostNotFoundException.class); - } - - @Test - @DisplayName("동일한 게시글을 한 번 이상 신고하면 예외를 발생한다.") - void reportPost_exception_duplicated() { - ReportRequest request = ReportRequest.builder() - .message("report message") - .build(); - - when(postRepository.findById(any(Long.class))) - .thenReturn(Optional.ofNullable(testPost)); - reportService.reportPost(1L, request, testAuthInfo); - - assertThatThrownBy(() -> reportService.reportPost(1L, request, testAuthInfo)) - .isInstanceOf(DuplicatedPostReportException.class); - } - - @Test - @DisplayName("유효한 id를 통해 특정 댓글을 신고한다.") - void reportComment() { - ReportRequest request = ReportRequest.builder() - .message("report message") - .build(); - - when(commentRepository.findById(any(Long.class))) - .thenReturn(Optional.ofNullable(testComment)); - - reportService.reportComment(1L, request, testAuthInfo); - - assertThat(testComment.getReports().size()).isEqualTo(1); - } - - @Test - @DisplayName("신고하려는 댓글의 id가 유효하지 않으면 예외를 발생한다.") - void reportComment_exception_invalidId() { - ReportRequest request = ReportRequest.builder() - .message("report message") - .build(); - - when(commentRepository.findById(any(Long.class))) - .thenThrow(new CommentNotFoundException()); - - assertThatThrownBy(() -> reportService.reportComment(1L, request, testAuthInfo)) - .isInstanceOf(CommentNotFoundException.class); - } - - @Test - @DisplayName("동일한 댓글을 한 번 이상 신고하면 예외를 발생한다.") - void reportComment_exception_duplicated() { - ReportRequest request = ReportRequest.builder() - .message("report message") - .build(); - - when(commentRepository.findById(any(Long.class))) - .thenReturn(Optional.ofNullable(testComment)); - reportService.reportComment(1L, request, testAuthInfo); - - assertThatThrownBy(() -> reportService.reportComment(1L, request, testAuthInfo)) - .isInstanceOf(DuplicatedCommentReportException.class); - } - -} \ No newline at end of file diff --git a/src/test/java/org/wooriverygood/api/util/ApiTest.java b/src/test/java/org/wooriverygood/api/util/ApiTest.java index cc8a9ff..a848eb7 100644 --- a/src/test/java/org/wooriverygood/api/util/ApiTest.java +++ b/src/test/java/org/wooriverygood/api/util/ApiTest.java @@ -15,11 +15,12 @@ import org.wooriverygood.api.comment.service.CommentService; import org.wooriverygood.api.course.application.CourseFindService; import org.wooriverygood.api.post.application.*; -import org.wooriverygood.api.report.controller.ReportController; -import org.wooriverygood.api.report.service.ReportService; +import org.wooriverygood.api.report.api.ReportApi; +import org.wooriverygood.api.report.application.CommentReportService; import org.wooriverygood.api.course.api.CourseApi; import org.wooriverygood.api.course.application.CourseCreateService; import org.wooriverygood.api.post.api.PostApi; +import org.wooriverygood.api.report.application.PostReportService; import org.wooriverygood.api.review.api.ReviewApi; import org.wooriverygood.api.review.application.*; import org.wooriverygood.api.global.auth.AuthInfo; @@ -37,7 +38,7 @@ PostApi.class, CourseApi.class, ReviewApi.class, - ReportController.class + ReportApi.class }) @WithMockUser @ExtendWith(RestDocumentationExtension.class) @@ -88,7 +89,10 @@ public class ApiTest { protected PostDeleteService postDeleteService; @MockBean - protected ReportService reportService; + protected CommentReportService commentReportService; + + @MockBean + protected PostReportService postReportService; @MockBean protected AuthenticationPrincipalArgumentResolver authenticationPrincipalArgumentResolver; diff --git a/src/test/java/org/wooriverygood/api/util/ResponseFixture.java b/src/test/java/org/wooriverygood/api/util/ResponseFixture.java index 81e73ee..0925a3d 100644 --- a/src/test/java/org/wooriverygood/api/util/ResponseFixture.java +++ b/src/test/java/org/wooriverygood/api/util/ResponseFixture.java @@ -70,19 +70,5 @@ public static PostsResponse postsResponse(int page, int totalCount, AuthInfo aut .build(); } - public static PostsResponse postsResponse(int page, int totalCount, String category, AuthInfo authInfo) { - List responses = new ArrayList<>(); - int bound = Math.min(totalCount - page * 10 + 1, 10); - int start = page * 10 + 1; - for (int i = start; i < start + bound; i++) { - if (i % 5 == 0) responses.add(reportedPostResponse(i, authInfo)); - else responses.add(postResponse(i, authInfo)); - } - return PostsResponse.builder() - .posts(responses) - .totalPostCount(totalCount) - .totalPageCount((int) Math.ceil((double) totalCount / 10)) - .build(); - } } From f8ac80cd82f8014d68b0749272fef02be1543c2c Mon Sep 17 00:00:00 2001 From: Park Geonho Date: Tue, 2 Apr 2024 22:46:33 +0800 Subject: [PATCH 14/21] =?UTF-8?q?[refactor]=20=EB=8C=93=EA=B8=80=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/comment/api/CommentApi.java | 73 +++++ .../application/CommentCreateService.java | 58 ++++ .../application/CommentDeleteService.java | 57 ++++ .../application/CommentFindService.java | 56 ++++ .../application/CommentLikeToggleService.java | 66 ++++ .../application/CommentUpdateService.java | 29 ++ .../comment/controller/CommentController.java | 69 ---- .../api/comment/domain/Comment.java | 8 + .../comment/dto/CommentDeleteResponse.java | 15 - .../api/comment/dto/CommentLikeResponse.java | 10 +- .../api/comment/dto/CommentResponse.java | 64 ++-- .../comment/dto/CommentUpdateResponse.java | 15 - .../api/comment/dto/CommentsResponse.java | 17 + .../api/comment/dto/NewCommentResponse.java | 28 -- .../api/comment/dto/ReplyResponse.java | 39 +-- .../api/comment/service/CommentService.java | 186 ----------- .../{controller => api}/CommentApiTest.java | 89 ++--- .../application/CommentCreateServiceTest.java | 116 +++++++ .../application/CommentDeleteServiceTest.java | 132 ++++++++ .../application/CommentFindServiceTest.java | 146 +++++++++ .../CommentLikeToggleServiceTest.java | 114 +++++++ .../application/CommentUpdateServiceTest.java | 91 ++++++ .../comment/service/CommentServiceTest.java | 303 ------------------ .../org/wooriverygood/api/util/ApiTest.java | 20 +- .../org/wooriverygood/api/util/MockTest.java | 6 + 25 files changed, 1080 insertions(+), 727 deletions(-) create mode 100644 src/main/java/org/wooriverygood/api/comment/api/CommentApi.java create mode 100644 src/main/java/org/wooriverygood/api/comment/application/CommentCreateService.java create mode 100644 src/main/java/org/wooriverygood/api/comment/application/CommentDeleteService.java create mode 100644 src/main/java/org/wooriverygood/api/comment/application/CommentFindService.java create mode 100644 src/main/java/org/wooriverygood/api/comment/application/CommentLikeToggleService.java create mode 100644 src/main/java/org/wooriverygood/api/comment/application/CommentUpdateService.java delete mode 100644 src/main/java/org/wooriverygood/api/comment/controller/CommentController.java delete mode 100644 src/main/java/org/wooriverygood/api/comment/dto/CommentDeleteResponse.java delete mode 100644 src/main/java/org/wooriverygood/api/comment/dto/CommentUpdateResponse.java create mode 100644 src/main/java/org/wooriverygood/api/comment/dto/CommentsResponse.java delete mode 100644 src/main/java/org/wooriverygood/api/comment/dto/NewCommentResponse.java delete mode 100644 src/main/java/org/wooriverygood/api/comment/service/CommentService.java rename src/test/java/org/wooriverygood/api/comment/{controller => api}/CommentApiTest.java (73%) create mode 100644 src/test/java/org/wooriverygood/api/comment/application/CommentCreateServiceTest.java create mode 100644 src/test/java/org/wooriverygood/api/comment/application/CommentDeleteServiceTest.java create mode 100644 src/test/java/org/wooriverygood/api/comment/application/CommentFindServiceTest.java create mode 100644 src/test/java/org/wooriverygood/api/comment/application/CommentLikeToggleServiceTest.java create mode 100644 src/test/java/org/wooriverygood/api/comment/application/CommentUpdateServiceTest.java delete mode 100644 src/test/java/org/wooriverygood/api/comment/service/CommentServiceTest.java diff --git a/src/main/java/org/wooriverygood/api/comment/api/CommentApi.java b/src/main/java/org/wooriverygood/api/comment/api/CommentApi.java new file mode 100644 index 0000000..ddcdee2 --- /dev/null +++ b/src/main/java/org/wooriverygood/api/comment/api/CommentApi.java @@ -0,0 +1,73 @@ +package org.wooriverygood.api.comment.api; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.wooriverygood.api.comment.application.*; +import org.wooriverygood.api.comment.dto.*; +import org.wooriverygood.api.global.auth.AuthInfo; +import org.wooriverygood.api.global.auth.Login; + +@RestController +@RequiredArgsConstructor +public class CommentApi { + + private final CommentLikeToggleService commentLikeToggleService; + + private final CommentFindService commentFindService; + + private final CommentCreateService commentCreateService; + + private final CommentDeleteService commentDeleteService; + + private final CommentUpdateService commentUpdateService; + + + @GetMapping("/posts/{id}/comments") + public ResponseEntity findAllCommentsByPostId(@PathVariable("id") Long postId, + @Login AuthInfo authInfo) { + CommentsResponse response = commentFindService.findAllCommentsByPostId(postId, authInfo); + return ResponseEntity.ok(response); + } + + @PostMapping("/posts/{id}/comments") + public ResponseEntity addComment(@PathVariable("id") Long postId, + @Login AuthInfo authInfo, + @Valid @RequestBody NewCommentRequest newCommentRequest) { + commentCreateService.addComment(authInfo, postId, newCommentRequest); + return ResponseEntity.status(HttpStatus.CREATED).build(); + } + + @PutMapping("/comments/{id}/like") + public ResponseEntity toggleCommentLike(@PathVariable("id") Long commentId, + @Login AuthInfo authInfo) { + CommentLikeResponse response = commentLikeToggleService.likeComment(commentId, authInfo); + return ResponseEntity.ok(response); + } + + @PutMapping("/comments/{id}") + public ResponseEntity updateComment(@PathVariable("id") Long commentId, + @Valid @RequestBody CommentUpdateRequest request, + @Login AuthInfo authInfo) { + commentUpdateService.updateComment(commentId, request, authInfo); + return ResponseEntity.noContent().build(); + } + + @DeleteMapping("/comments/{id}") + public ResponseEntity deleteComment(@PathVariable("id") Long commentId, + @Login AuthInfo authInfo) { + commentDeleteService.deleteComment(commentId, authInfo); + return ResponseEntity.noContent().build(); + } + + @PostMapping("/comments/{id}/reply") + public ResponseEntity addReply(@PathVariable("id") Long commentId, + @RequestBody NewReplyRequest request, + @Login AuthInfo authInfo) { + commentCreateService.addReply(commentId, request, authInfo); + return ResponseEntity.status(HttpStatus.CREATED).build(); + } + +} diff --git a/src/main/java/org/wooriverygood/api/comment/application/CommentCreateService.java b/src/main/java/org/wooriverygood/api/comment/application/CommentCreateService.java new file mode 100644 index 0000000..2071396 --- /dev/null +++ b/src/main/java/org/wooriverygood/api/comment/application/CommentCreateService.java @@ -0,0 +1,58 @@ +package org.wooriverygood.api.comment.application; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.wooriverygood.api.advice.exception.CommentNotFoundException; +import org.wooriverygood.api.advice.exception.ReplyDepthException; +import org.wooriverygood.api.comment.domain.Comment; +import org.wooriverygood.api.comment.dto.NewCommentRequest; +import org.wooriverygood.api.comment.dto.NewReplyRequest; +import org.wooriverygood.api.comment.repository.CommentRepository; +import org.wooriverygood.api.global.auth.AuthInfo; +import org.wooriverygood.api.post.domain.Post; +import org.wooriverygood.api.post.exception.PostNotFoundException; +import org.wooriverygood.api.post.repository.PostRepository; + +@Service +@Transactional +@RequiredArgsConstructor +public class CommentCreateService { + + private final PostRepository postRepository; + + private final CommentRepository commentRepository; + + + public void addComment(AuthInfo authInfo, Long postId, NewCommentRequest request) { + Post post = postRepository.findById(postId) + .orElseThrow(PostNotFoundException::new); + + Comment comment = Comment.builder() + .content(request.getContent()) + .author(authInfo.getUsername()) + .post(post) + .build(); + + commentRepository.save(comment); + } + + public void addReply(Long commentId, NewReplyRequest request, AuthInfo authInfo) { + Comment parent = commentRepository.findById(commentId) + .orElseThrow(CommentNotFoundException::new); + + if (!parent.isParent()) + throw new ReplyDepthException(); + + Comment child = Comment.builder() + .content(request.getContent()) + .author(authInfo.getUsername()) + .post(parent.getPost()) + .parent(parent) + .build(); + parent.addChildren(child); + + commentRepository.save(child); + } + +} diff --git a/src/main/java/org/wooriverygood/api/comment/application/CommentDeleteService.java b/src/main/java/org/wooriverygood/api/comment/application/CommentDeleteService.java new file mode 100644 index 0000000..1407520 --- /dev/null +++ b/src/main/java/org/wooriverygood/api/comment/application/CommentDeleteService.java @@ -0,0 +1,57 @@ +package org.wooriverygood.api.comment.application; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.wooriverygood.api.advice.exception.CommentNotFoundException; +import org.wooriverygood.api.comment.domain.Comment; +import org.wooriverygood.api.comment.repository.CommentLikeRepository; +import org.wooriverygood.api.comment.repository.CommentRepository; +import org.wooriverygood.api.global.auth.AuthInfo; + +@Service +@Transactional +@RequiredArgsConstructor +public class CommentDeleteService { + + private final CommentRepository commentRepository; + + private final CommentLikeRepository commentLikeRepository; + + + public void deleteComment(Long commentId, AuthInfo authInfo) { + Comment comment = commentRepository.findById(commentId) + .orElseThrow(CommentNotFoundException::new); + + comment.validateAuthor(authInfo.getUsername()); + + commentLikeRepository.deleteAllByComment(comment); + deleteCommentOrReply(comment); + } + + private void deleteCommentOrReply(Comment comment) { + if (comment.isParent()) { + deleteParent(comment); + return; + } + deleteChild(comment); + } + + private void deleteParent(Comment parent) { + if (parent.hasNoReply()) { + commentRepository.delete(parent); + return; + } + parent.willBeDeleted(); + } + + private void deleteChild(Comment reply) { + Comment parent = reply.getParent(); + parent.deleteChild(reply); + commentRepository.delete(reply); + + if (parent.canDelete()) + commentRepository.delete(parent); + } + +} diff --git a/src/main/java/org/wooriverygood/api/comment/application/CommentFindService.java b/src/main/java/org/wooriverygood/api/comment/application/CommentFindService.java new file mode 100644 index 0000000..a9c2a51 --- /dev/null +++ b/src/main/java/org/wooriverygood/api/comment/application/CommentFindService.java @@ -0,0 +1,56 @@ +package org.wooriverygood.api.comment.application; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.wooriverygood.api.comment.domain.Comment; +import org.wooriverygood.api.comment.dto.CommentResponse; +import org.wooriverygood.api.comment.dto.CommentsResponse; +import org.wooriverygood.api.comment.dto.ReplyResponse; +import org.wooriverygood.api.comment.repository.CommentLikeRepository; +import org.wooriverygood.api.comment.repository.CommentRepository; +import org.wooriverygood.api.global.auth.AuthInfo; + +import java.util.List; +import java.util.Objects; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class CommentFindService { + + private final CommentRepository commentRepository; + + private final CommentLikeRepository commentLikeRepository; + + + public CommentsResponse findAllCommentsByPostId(Long postId, AuthInfo authInfo) { + List comments = commentRepository.findAllByPostId(postId); + List responses = comments.stream() + .map(comment -> convertToCommentResponse(comment, authInfo)) + .filter(response -> !Objects.isNull(response)) + .toList(); + return new CommentsResponse(responses); + } + + private CommentResponse convertToCommentResponse(Comment comment, AuthInfo authInfo) { + if (comment.isReply()) + return null; + if (comment.isSoftRemoved()) + return CommentResponse.softRemovedOf(comment, convertToReplyResponses(comment, authInfo)); + + boolean liked = commentLikeRepository.existsByCommentAndUsername(comment, authInfo.getUsername()); + return CommentResponse.of(comment, convertToReplyResponses(comment, authInfo), liked); + } + + private List convertToReplyResponses(Comment parent, AuthInfo authInfo) { + return parent.getChildren() + .stream() + .map(reply -> { + boolean liked = commentLikeRepository.existsByCommentAndUsername(reply, authInfo.getUsername()); + return ReplyResponse.from(reply, liked); + }) + .toList(); + } + +} diff --git a/src/main/java/org/wooriverygood/api/comment/application/CommentLikeToggleService.java b/src/main/java/org/wooriverygood/api/comment/application/CommentLikeToggleService.java new file mode 100644 index 0000000..2bc379b --- /dev/null +++ b/src/main/java/org/wooriverygood/api/comment/application/CommentLikeToggleService.java @@ -0,0 +1,66 @@ +package org.wooriverygood.api.comment.application; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.wooriverygood.api.comment.domain.Comment; +import org.wooriverygood.api.comment.domain.CommentLike; +import org.wooriverygood.api.comment.dto.*; +import org.wooriverygood.api.comment.repository.CommentLikeRepository; +import org.wooriverygood.api.comment.repository.CommentRepository; +import org.wooriverygood.api.advice.exception.CommentNotFoundException; +import org.wooriverygood.api.global.auth.AuthInfo; + +import java.util.Optional; + +@Service +@Transactional +@RequiredArgsConstructor +public class CommentLikeToggleService { + + + private final CommentRepository commentRepository; + + private final CommentLikeRepository commentLikeRepository; + + + public CommentLikeResponse likeComment(Long commentId, AuthInfo authInfo) { + Comment comment = commentRepository.findById(commentId) + .orElseThrow(CommentNotFoundException::new); + + Optional commentLike = commentLikeRepository.findByCommentAndUsername(comment, authInfo.getUsername()); + + if (commentLike.isEmpty()) { + addCommentLike(comment, authInfo.getUsername()); + return createCommentLikeResponse(comment, true); + } + + deleteCommentLike(comment, commentLike.get()); + return createCommentLikeResponse(comment, false); + } + + private void addCommentLike(Comment comment, String username) { + CommentLike commentLike = CommentLike.builder() + .comment(comment) + .username(username) + .build(); + + comment.addCommentLike(commentLike); + commentLikeRepository.save(commentLike); + commentRepository.increaseLikeCount(comment.getId()); + } + + private void deleteCommentLike(Comment comment, CommentLike commentLike) { + comment.deleteCommentLike(commentLike); + commentRepository.decreaseLikeCount(comment.getId()); + } + + private CommentLikeResponse createCommentLikeResponse(Comment comment, boolean liked) { + int likeCount = comment.getLikeCount() + (liked ? 1 : -1); + return CommentLikeResponse.builder() + .likeCount(likeCount) + .liked(liked) + .build(); + } + +} diff --git a/src/main/java/org/wooriverygood/api/comment/application/CommentUpdateService.java b/src/main/java/org/wooriverygood/api/comment/application/CommentUpdateService.java new file mode 100644 index 0000000..b2faea0 --- /dev/null +++ b/src/main/java/org/wooriverygood/api/comment/application/CommentUpdateService.java @@ -0,0 +1,29 @@ +package org.wooriverygood.api.comment.application; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.wooriverygood.api.advice.exception.CommentNotFoundException; +import org.wooriverygood.api.comment.domain.Comment; +import org.wooriverygood.api.comment.dto.CommentUpdateRequest; +import org.wooriverygood.api.comment.repository.CommentRepository; +import org.wooriverygood.api.global.auth.AuthInfo; + +@Service +@Transactional +@RequiredArgsConstructor +public class CommentUpdateService { + + private final CommentRepository commentRepository; + + + public void updateComment(Long commentId, CommentUpdateRequest request, AuthInfo authInfo) { + Comment comment = commentRepository.findById(commentId) + .orElseThrow(CommentNotFoundException::new); + + comment.validateAuthor(authInfo.getUsername()); + + comment.updateContent(request.getContent()); + } + +} diff --git a/src/main/java/org/wooriverygood/api/comment/controller/CommentController.java b/src/main/java/org/wooriverygood/api/comment/controller/CommentController.java deleted file mode 100644 index e848243..0000000 --- a/src/main/java/org/wooriverygood/api/comment/controller/CommentController.java +++ /dev/null @@ -1,69 +0,0 @@ -package org.wooriverygood.api.comment.controller; - -import jakarta.validation.Valid; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; -import org.wooriverygood.api.comment.dto.*; -import org.wooriverygood.api.comment.service.CommentService; -import org.wooriverygood.api.global.auth.AuthInfo; -import org.wooriverygood.api.global.auth.Login; - -import java.util.List; - -@RestController -public class CommentController { - - private final CommentService commentService; - - - public CommentController(CommentService commentService) { - this.commentService = commentService; - } - - @GetMapping("/community/{id}/comments") - public ResponseEntity> findAllCommentsByPostId(@PathVariable("id") Long postId, - @Login AuthInfo authInfo) { - List responses = commentService.findAllComments(postId, authInfo); - return ResponseEntity.ok(responses); - } - - @PostMapping("/community/{id}/comments") - public ResponseEntity addComment(@PathVariable("id") Long postId, - @Login AuthInfo authInfo, - @Valid @RequestBody NewCommentRequest newCommentRequest) { - NewCommentResponse response = commentService.addComment(authInfo, postId, newCommentRequest); - return ResponseEntity.status(HttpStatus.CREATED).body(response); - } - - @PutMapping("/comments/{id}/like") - public ResponseEntity likeComment(@PathVariable("id") Long commentId, - @Login AuthInfo authInfo) { - CommentLikeResponse response = commentService.likeComment(commentId, authInfo); - return ResponseEntity.ok(response); - } - - @PutMapping("/comments/{id}") - public ResponseEntity updateComment(@PathVariable("id") Long commentId, - @Valid @RequestBody CommentUpdateRequest request, - @Login AuthInfo authInfo) { - CommentUpdateResponse response = commentService.updateComment(commentId, request, authInfo); - return ResponseEntity.ok(response); - } - - @DeleteMapping("/comments/{id}") - public ResponseEntity deleteComment(@PathVariable("id") Long commentId, - @Login AuthInfo authInfo) { - CommentDeleteResponse response = commentService.deleteComment(commentId, authInfo); - return ResponseEntity.ok(response); - } - - @PostMapping("/comments/{id}/reply") - public ResponseEntity addReply(@PathVariable("id") Long commentId, - @RequestBody NewReplyRequest request, - @Login AuthInfo authInfo) { - commentService.addReply(commentId, request, authInfo); - return ResponseEntity.status(HttpStatus.CREATED).build(); - } - -} diff --git a/src/main/java/org/wooriverygood/api/comment/domain/Comment.java b/src/main/java/org/wooriverygood/api/comment/domain/Comment.java index 436d88e..6c9d0c2 100644 --- a/src/main/java/org/wooriverygood/api/comment/domain/Comment.java +++ b/src/main/java/org/wooriverygood/api/comment/domain/Comment.java @@ -143,4 +143,12 @@ public boolean canDelete() { return hasNoReply() && softRemoved; } + public boolean isReportedTooMuch() { + return reportCount >= 5; + } + + public String getContent() { + return isReportedTooMuch() ? null : content; + } + } diff --git a/src/main/java/org/wooriverygood/api/comment/dto/CommentDeleteResponse.java b/src/main/java/org/wooriverygood/api/comment/dto/CommentDeleteResponse.java deleted file mode 100644 index 9fb453e..0000000 --- a/src/main/java/org/wooriverygood/api/comment/dto/CommentDeleteResponse.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.wooriverygood.api.comment.dto; - -import lombok.Builder; -import lombok.Getter; - -@Getter -public class CommentDeleteResponse { - - private final Long comment_id; - - @Builder - public CommentDeleteResponse(Long comment_id) { - this.comment_id = comment_id; - } -} diff --git a/src/main/java/org/wooriverygood/api/comment/dto/CommentLikeResponse.java b/src/main/java/org/wooriverygood/api/comment/dto/CommentLikeResponse.java index dad11f8..872cdf3 100644 --- a/src/main/java/org/wooriverygood/api/comment/dto/CommentLikeResponse.java +++ b/src/main/java/org/wooriverygood/api/comment/dto/CommentLikeResponse.java @@ -1,18 +1,22 @@ package org.wooriverygood.api.comment.dto; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; import lombok.Builder; import lombok.Getter; @Getter +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) public class CommentLikeResponse { - private final int like_count; + private final int likeCount; private final boolean liked; + @Builder - public CommentLikeResponse(int like_count, boolean liked) { - this.like_count = like_count; + public CommentLikeResponse(int likeCount, boolean liked) { + this.likeCount = likeCount; this.liked = liked; } diff --git a/src/main/java/org/wooriverygood/api/comment/dto/CommentResponse.java b/src/main/java/org/wooriverygood/api/comment/dto/CommentResponse.java index d8bb30b..91b790b 100644 --- a/src/main/java/org/wooriverygood/api/comment/dto/CommentResponse.java +++ b/src/main/java/org/wooriverygood/api/comment/dto/CommentResponse.java @@ -1,5 +1,7 @@ package org.wooriverygood.api.comment.dto; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; import lombok.Builder; import lombok.Getter; import org.wooriverygood.api.comment.domain.Comment; @@ -8,19 +10,20 @@ import java.util.List; @Getter +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) public class CommentResponse { - private final Long comment_id; + private final Long commentId; - private final String comment_content; + private final String commentContent; - private final String comment_author; + private final String commentAuthor; - private final Long post_id; + private final Long postId; - private final int comment_likes; + private final int commentLikeCount; - private final LocalDateTime comment_time; + private final LocalDateTime commentTime; private final boolean liked; @@ -32,13 +35,13 @@ public class CommentResponse { @Builder - public CommentResponse(Long comment_id, String comment_content, String comment_author, Long post_id, int comment_likes, LocalDateTime comment_time, boolean liked, List replies, boolean updated, boolean reported) { - this.comment_id = comment_id; - this.comment_content = comment_content; - this.comment_author = comment_author; - this.post_id = post_id; - this.comment_likes = comment_likes; - this.comment_time = comment_time; + public CommentResponse(Long commentId, String commentContent, String commentAuthor, Long postId, int commentLikeCount, LocalDateTime commentTime, boolean liked, List replies, boolean updated, boolean reported) { + this.commentId = commentId; + this.commentContent = commentContent; + this.commentAuthor = commentAuthor; + this.postId = postId; + this.commentLikeCount = commentLikeCount; + this.commentTime = commentTime; this.liked = liked; this.replies = replies; this.updated = updated; @@ -46,34 +49,33 @@ public CommentResponse(Long comment_id, String comment_content, String comment_a } - public static CommentResponse from(Comment comment, List replies, boolean liked) { - boolean reported = comment.getReportCount() >= 5; + public static CommentResponse of(Comment comment, List replies, boolean liked) { return CommentResponse.builder() - .comment_id(comment.getId()) - .comment_content(reported ? null : comment.getContent()) - .comment_author(comment.getAuthor()) - .post_id(comment.getPost().getId()) - .comment_likes(comment.getLikeCount()) - .comment_time(comment.getCreatedAt()) + .commentId(comment.getId()) + .commentContent(comment.getContent()) + .commentAuthor(comment.getAuthor()) + .postId(comment.getPost().getId()) + .commentLikeCount(comment.getLikeCount()) + .commentTime(comment.getCreatedAt()) .liked(liked) .replies(replies) .updated(comment.isUpdated()) - .reported(reported) + .reported(comment.isReportedTooMuch()) .build(); } - public static CommentResponse softRemovedFrom(Comment comment, List replies) { - boolean reported = comment.getReportCount() >= 5; + public static CommentResponse softRemovedOf(Comment comment, List replies) { return CommentResponse.builder() - .comment_id(comment.getId()) - .comment_content(null) - .comment_author(comment.getAuthor()) - .post_id(comment.getPost().getId()) - .comment_likes(comment.getLikeCount()) - .comment_time(comment.getCreatedAt()) + .commentId(comment.getId()) + .commentContent(null) + .commentAuthor(comment.getAuthor()) + .postId(comment.getPost().getId()) + .commentLikeCount(comment.getLikeCount()) + .commentTime(comment.getCreatedAt()) .replies(replies) .updated(comment.isUpdated()) - .reported(reported) + .reported(comment.isReportedTooMuch()) .build(); } + } diff --git a/src/main/java/org/wooriverygood/api/comment/dto/CommentUpdateResponse.java b/src/main/java/org/wooriverygood/api/comment/dto/CommentUpdateResponse.java deleted file mode 100644 index bd85f6f..0000000 --- a/src/main/java/org/wooriverygood/api/comment/dto/CommentUpdateResponse.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.wooriverygood.api.comment.dto; - -import lombok.Builder; -import lombok.Getter; - -@Getter -public class CommentUpdateResponse { - - private final Long comment_id; - - @Builder - public CommentUpdateResponse(Long comment_id) { - this.comment_id = comment_id; - } -} diff --git a/src/main/java/org/wooriverygood/api/comment/dto/CommentsResponse.java b/src/main/java/org/wooriverygood/api/comment/dto/CommentsResponse.java new file mode 100644 index 0000000..fa05254 --- /dev/null +++ b/src/main/java/org/wooriverygood/api/comment/dto/CommentsResponse.java @@ -0,0 +1,17 @@ +package org.wooriverygood.api.comment.dto; + +import lombok.Getter; + +import java.util.List; + +@Getter +public class CommentsResponse { + + private final List comments; + + + public CommentsResponse(List comments) { + this.comments = comments; + } + +} diff --git a/src/main/java/org/wooriverygood/api/comment/dto/NewCommentResponse.java b/src/main/java/org/wooriverygood/api/comment/dto/NewCommentResponse.java deleted file mode 100644 index 5b8a764..0000000 --- a/src/main/java/org/wooriverygood/api/comment/dto/NewCommentResponse.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.wooriverygood.api.comment.dto; - -import lombok.Builder; -import lombok.Getter; -import org.wooriverygood.api.comment.domain.Comment; - -@Getter -public class NewCommentResponse { - - private Long comment_id; - private String content; - private String author; - - @Builder - public NewCommentResponse(Long comment_id, String content, String author) { - this.comment_id = comment_id; - this.content = content; - this.author = author; - } - - static public NewCommentResponse of(Comment comment) { - return NewCommentResponse.builder() - .comment_id(comment.getId()) - .content(comment.getContent()) - .author(comment.getAuthor()) - .build(); - } -} diff --git a/src/main/java/org/wooriverygood/api/comment/dto/ReplyResponse.java b/src/main/java/org/wooriverygood/api/comment/dto/ReplyResponse.java index eda61ed..c9a5566 100644 --- a/src/main/java/org/wooriverygood/api/comment/dto/ReplyResponse.java +++ b/src/main/java/org/wooriverygood/api/comment/dto/ReplyResponse.java @@ -1,5 +1,7 @@ package org.wooriverygood.api.comment.dto; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; import lombok.Builder; import lombok.Getter; import org.wooriverygood.api.comment.domain.Comment; @@ -7,16 +9,18 @@ import java.time.LocalDateTime; @Getter +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) public class ReplyResponse { - private final Long reply_id; - private final String reply_content; + private final Long replyId; - private final String reply_author; + private final String replyContent; - private final int reply_likes; + private final String replyAuthor; - private final LocalDateTime reply_time; + private final int replyLikeCount; + + private final LocalDateTime replyTime; private final boolean liked; @@ -27,28 +31,27 @@ public class ReplyResponse { @Builder - public ReplyResponse(Long reply_id, String reply_content, String reply_author, int reply_likes, LocalDateTime reply_time, boolean liked, boolean updated, boolean reported) { - this.reply_id = reply_id; - this.reply_content = reply_content; - this.reply_author = reply_author; - this.reply_likes = reply_likes; - this.reply_time = reply_time; + public ReplyResponse(Long replyId, String replyContent, String replyAuthor, int replyLikeCount, LocalDateTime replyTime, boolean liked, boolean updated, boolean reported) { + this.replyId = replyId; + this.replyContent = replyContent; + this.replyAuthor = replyAuthor; + this.replyLikeCount = replyLikeCount; + this.replyTime = replyTime; this.liked = liked; this.updated = updated; this.reported = reported; } public static ReplyResponse from(Comment reply, boolean liked) { - boolean reported = reply.getReportCount() >= 5; return ReplyResponse.builder() - .reply_id(reply.getId()) - .reply_content(reported ? null : reply.getContent()) - .reply_author(reply.getAuthor()) - .reply_likes(reply.getLikeCount()) - .reply_time(reply.getCreatedAt()) + .replyId(reply.getId()) + .replyContent(reply.getContent()) + .replyAuthor(reply.getAuthor()) + .replyLikeCount(reply.getLikeCount()) + .replyTime(reply.getCreatedAt()) .liked(liked) .updated(reply.isUpdated()) - .reported(reported) + .reported(reply.isReportedTooMuch()) .build(); } diff --git a/src/main/java/org/wooriverygood/api/comment/service/CommentService.java b/src/main/java/org/wooriverygood/api/comment/service/CommentService.java deleted file mode 100644 index 552d47d..0000000 --- a/src/main/java/org/wooriverygood/api/comment/service/CommentService.java +++ /dev/null @@ -1,186 +0,0 @@ -package org.wooriverygood.api.comment.service; - -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.wooriverygood.api.advice.exception.ReplyDepthException; -import org.wooriverygood.api.comment.domain.Comment; -import org.wooriverygood.api.comment.domain.CommentLike; -import org.wooriverygood.api.comment.dto.*; -import org.wooriverygood.api.comment.repository.CommentLikeRepository; -import org.wooriverygood.api.comment.repository.CommentRepository; -import org.wooriverygood.api.advice.exception.CommentNotFoundException; -import org.wooriverygood.api.post.exception.PostNotFoundException; -import org.wooriverygood.api.post.domain.Post; -import org.wooriverygood.api.post.repository.PostRepository; -import org.wooriverygood.api.global.auth.AuthInfo; - -import java.util.List; -import java.util.Objects; -import java.util.Optional; - -@Service -@Transactional(readOnly = true) -public class CommentService { - - private final CommentRepository commentRepository; - private final PostRepository postRepository; - private final CommentLikeRepository commentLikeRepository; - - - public CommentService(CommentRepository commentRepository, PostRepository postRepository, CommentLikeRepository commentLikeRepository) { - this.commentRepository = commentRepository; - this.postRepository = postRepository; - this.commentLikeRepository = commentLikeRepository; - } - - public List findAllComments(Long postId, AuthInfo authInfo) { - List comments = commentRepository.findAllByPostId(postId); - return comments.stream().map(comment -> convertToCommentResponse(comment, authInfo)) - .filter(response -> !Objects.isNull(response)) - .toList(); - } - - private CommentResponse convertToCommentResponse(Comment comment, AuthInfo authInfo) { - if (comment.isReply()) - return null; - if (comment.isSoftRemoved()) - return CommentResponse.softRemovedFrom(comment, convertToReplyResponses(comment, authInfo)); - - boolean liked = commentLikeRepository.existsByCommentAndUsername(comment, authInfo.getUsername()); - return CommentResponse.from(comment, convertToReplyResponses(comment, authInfo), liked); - } - - private List convertToReplyResponses(Comment parent, AuthInfo authInfo) { - return parent.getChildren().stream().map(reply -> { - boolean liked = commentLikeRepository.existsByCommentAndUsername(reply, authInfo.getUsername()); - return ReplyResponse.from(reply, liked); - }).toList(); - } - - @Transactional - public NewCommentResponse addComment(AuthInfo authInfo, Long postId, NewCommentRequest newCommentRequest) { - Post post = postRepository.findById(postId) - .orElseThrow(PostNotFoundException::new); - - Comment comment = Comment.builder() - .content(newCommentRequest.getContent()) - .author(authInfo.getUsername()) - .post(post) - .build(); - Comment saved = commentRepository.save(comment); - - return NewCommentResponse.of(saved); - } - - @Transactional - public CommentLikeResponse likeComment(Long commentId, AuthInfo authInfo) { - Comment comment = commentRepository.findById(commentId) - .orElseThrow(CommentNotFoundException::new); - - Optional commentLike = commentLikeRepository.findByCommentAndUsername(comment, authInfo.getUsername()); - - if (commentLike.isEmpty()) { - addCommentLike(comment, authInfo.getUsername()); - return createCommentLikeResponse(comment, true); - } - - deleteCommentLike(comment, commentLike.get()); - return createCommentLikeResponse(comment, false); - } - - private void addCommentLike(Comment comment, String username) { - CommentLike commentLike = CommentLike.builder() - .comment(comment) - .username(username) - .build(); - - comment.addCommentLike(commentLike); - commentLikeRepository.save(commentLike); - commentRepository.increaseLikeCount(comment.getId()); - } - - private void deleteCommentLike(Comment comment, CommentLike commentLike) { - comment.deleteCommentLike(commentLike); - commentRepository.decreaseLikeCount(comment.getId()); - } - - private CommentLikeResponse createCommentLikeResponse(Comment comment, boolean liked) { - int likeCount = comment.getLikeCount() + (liked ? 1 : -1); - return CommentLikeResponse.builder() - .like_count(likeCount) - .liked(liked) - .build(); - } - - @Transactional - public CommentUpdateResponse updateComment(Long commentId, CommentUpdateRequest request, AuthInfo authInfo) { - Comment comment = commentRepository.findById(commentId) - .orElseThrow(CommentNotFoundException::new); - comment.validateAuthor(authInfo.getUsername()); - - comment.updateContent(request.getContent()); - - return CommentUpdateResponse.builder() - .comment_id(comment.getId()) - .build(); - } - - @Transactional - public CommentDeleteResponse deleteComment(Long commentId, AuthInfo authInfo) { - Comment comment = commentRepository.findById(commentId) - .orElseThrow(CommentNotFoundException::new); - - comment.validateAuthor(authInfo.getUsername()); - - commentLikeRepository.deleteAllByComment(comment); - deleteCommentOrReply(comment); - - return CommentDeleteResponse.builder() - .comment_id(commentId) - .build(); - } - - private void deleteCommentOrReply(Comment comment) { - if (comment.isParent()) { - deleteParent(comment); - return; - } - deleteChild(comment); - } - - private void deleteParent(Comment parent) { - if (parent.hasNoReply()) { - commentRepository.delete(parent); - return; - } - parent.willBeDeleted(); - } - - private void deleteChild(Comment reply) { - Comment parent = reply.getParent(); - parent.deleteChild(reply); - commentRepository.delete(reply); - - if (parent.canDelete()) - commentRepository.delete(parent); - } - - @Transactional - public void addReply(Long commentId, NewReplyRequest request, AuthInfo authInfo) { - Comment parent = commentRepository.findById(commentId) - .orElseThrow(CommentNotFoundException::new); - - if (!parent.isParent()) - throw new ReplyDepthException(); - - Comment child = Comment.builder() - .content(request.getContent()) - .author(authInfo.getUsername()) - .post(parent.getPost()) - .parent(parent) - .build(); - parent.addChildren(child); - - commentRepository.save(child); - } -} diff --git a/src/test/java/org/wooriverygood/api/comment/controller/CommentApiTest.java b/src/test/java/org/wooriverygood/api/comment/api/CommentApiTest.java similarity index 73% rename from src/test/java/org/wooriverygood/api/comment/controller/CommentApiTest.java rename to src/test/java/org/wooriverygood/api/comment/api/CommentApiTest.java index 93c89ee..bd554d9 100644 --- a/src/test/java/org/wooriverygood/api/comment/controller/CommentApiTest.java +++ b/src/test/java/org/wooriverygood/api/comment/api/CommentApiTest.java @@ -1,9 +1,8 @@ -package org.wooriverygood.api.comment.controller; +package org.wooriverygood.api.comment.api; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.wooriverygood.api.global.error.exception.AuthorizationException; @@ -19,16 +18,15 @@ import java.util.List; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.*; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; class CommentApiTest extends ApiTest { - List responses = new ArrayList<>(); + private List responses = new ArrayList<>(); - Post post = Post.builder() + private Post post = Post.builder() .id(1L) .category(PostCategory.OFFER) .title("title6") @@ -40,14 +38,14 @@ class CommentApiTest extends ApiTest { @BeforeEach void setUp() { - for (int i = 1; i <= 4; i++) { + for (long i = 1; i <= 4; i++) { responses.add(CommentResponse.builder() - .comment_id((long) i) - .comment_content("content" + i) - .comment_author("user-"+(i % 5)) - .post_id(post.getId()) - .comment_likes(i + 8) - .comment_time(LocalDateTime.now()) + .commentId(i) + .commentContent("content" + i) + .commentAuthor("user-"+(i % 5)) + .postId(post.getId()) + .commentLikeCount((int) i + 8) + .commentTime(LocalDateTime.now()) .liked(i % 3 == 0) .replies(new ArrayList<>()) .updated(i % 2 == 0) @@ -57,11 +55,11 @@ void setUp() { for (int i = 12; i <= 15; i++) { responses.get(2).getReplies() .add(ReplyResponse.builder() - .reply_id((long) i) - .reply_content("reply content " + i) - .reply_author("user-" + (i % 2)) - .reply_likes(i - 6) - .reply_time(LocalDateTime.now()) + .replyId((long) i) + .replyContent("reply content " + i) + .replyAuthor("user-" + (i % 2)) + .replyLikeCount(i - 6) + .replyTime(LocalDateTime.now()) .liked(false) .updated(i % 2 == 0) .reported(false) @@ -72,13 +70,13 @@ void setUp() { @Test @DisplayName("특정 게시글의 댓글 조회 요청을 받으면 댓글들을 반환한다.") void findAllCommentsByPostId() { - Mockito.when(commentService.findAllComments(any(Long.class), any(AuthInfo.class))) - .thenReturn(responses); + when(commentFindService.findAllCommentsByPostId(anyLong(), any(AuthInfo.class))) + .thenReturn(new CommentsResponse(responses)); restDocs .contentType(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer aws-cognito-access-token") - .when().get("/community/1/comments") + .when().get("/posts/1/comments") .then().log().all() .assertThat() .apply(document("comments/find/success")) @@ -92,20 +90,11 @@ void addComment() { .content("content51") .build(); - NewCommentResponse response = NewCommentResponse.builder() - .comment_id(51L) - .content("content51") - .author(testAuthInfo.getUsername()) - .build(); - - Mockito.when(commentService.addComment(any(AuthInfo.class), any(Long.class), any(NewCommentRequest.class))) - .thenReturn(response); - restDocs .contentType(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer aws-cognito-access-token") .body(request) - .when().post("/community/51/comments") + .when().post("/posts/51/comments") .then().log().all() .assertThat() .apply(document("comments/create/success")) @@ -115,9 +104,9 @@ void addComment() { @Test @DisplayName("특정 댓글의 좋아요를 1 올리거나 내린다.") void likeComment() { - Mockito.when(commentService.likeComment(any(Long.class), any(AuthInfo.class))) + when(commentLikeToggleService.likeComment(any(Long.class), any(AuthInfo.class))) .thenReturn(CommentLikeResponse.builder() - .like_count(5) + .likeCount(5) .liked(false) .build()); @@ -138,11 +127,6 @@ void updateComment() { .content("new comment content") .build(); - Mockito.when(commentService.updateComment(any(Long.class), any(CommentUpdateRequest.class), any(AuthInfo.class))) - .thenReturn(CommentUpdateResponse.builder() - .comment_id(2L) - .build()); - restDocs .contentType(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer aws-cognito-access-token") @@ -151,7 +135,7 @@ void updateComment() { .then().log().all() .assertThat() .apply(document("comments/update/success")) - .statusCode(HttpStatus.OK.value()); + .statusCode(HttpStatus.NO_CONTENT.value()); } @Test @@ -178,8 +162,9 @@ void updateComment_exception_noAuth() { .content("new comment content") .build(); - Mockito.when(commentService.updateComment(any(Long.class), any(CommentUpdateRequest.class), any(AuthInfo.class))) - .thenThrow(new AuthorizationException()); + doThrow(new AuthorizationException()) + .when(commentUpdateService) + .updateComment(anyLong(), any(CommentUpdateRequest.class), any(AuthInfo.class)); restDocs .contentType(MediaType.APPLICATION_JSON_VALUE) @@ -195,11 +180,6 @@ void updateComment_exception_noAuth() { @Test @DisplayName("권한이 있는 댓글을 삭제한다.") void deleteComment() { - Mockito.when(commentService.deleteComment(any(Long.class), any(AuthInfo.class))) - .thenReturn(CommentDeleteResponse.builder() - .comment_id(3L) - .build()); - restDocs .contentType(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer aws-cognito-access-token") @@ -207,14 +187,15 @@ void deleteComment() { .then().log().all() .assertThat() .apply(document("comments/delete/success")) - .statusCode(HttpStatus.OK.value()); + .statusCode(HttpStatus.NO_CONTENT.value()); } @Test @DisplayName("권한이 없는 댓글을 삭제하면 404를 반환한다.") void deleteComment_exception_noAuth() { - Mockito.when(commentService.deleteComment(any(Long.class), any(AuthInfo.class))) - .thenThrow(new AuthorizationException()); + doThrow(new AuthorizationException()) + .when(commentDeleteService) + .deleteComment(anyLong(), any(AuthInfo.class)); restDocs .contentType(MediaType.APPLICATION_JSON_VALUE) @@ -233,9 +214,6 @@ void addReply() { .content("reply content") .build(); - doNothing().when(commentService) - .addReply(any(Long.class), any(NewReplyRequest.class), any(AuthInfo.class)); - restDocs .contentType(MediaType.APPLICATION_JSON_VALUE) .header("Authorization", "Bearer aws-cognito-access-token") @@ -248,14 +226,15 @@ void addReply() { } @Test - @DisplayName("특정 댓글의 대댓글을 작성한다.") + @DisplayName("특정 대댓글의 대댓글을 작성하려고 하면, 400 에러를 반환한다.") void addReply_exception_depth() { NewReplyRequest request = NewReplyRequest.builder() .content("reply content") .build(); - doThrow(new ReplyDepthException()).when(commentService) - .addReply(any(Long.class), any(NewReplyRequest.class), any(AuthInfo.class)); + doThrow(new ReplyDepthException()) + .when(commentCreateService) + .addReply(anyLong(), any(NewReplyRequest.class), any(AuthInfo.class)); restDocs .contentType(MediaType.APPLICATION_JSON_VALUE) diff --git a/src/test/java/org/wooriverygood/api/comment/application/CommentCreateServiceTest.java b/src/test/java/org/wooriverygood/api/comment/application/CommentCreateServiceTest.java new file mode 100644 index 0000000..7ed936b --- /dev/null +++ b/src/test/java/org/wooriverygood/api/comment/application/CommentCreateServiceTest.java @@ -0,0 +1,116 @@ +package org.wooriverygood.api.comment.application; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.wooriverygood.api.advice.exception.ReplyDepthException; +import org.wooriverygood.api.comment.domain.Comment; +import org.wooriverygood.api.comment.dto.NewCommentRequest; +import org.wooriverygood.api.comment.dto.NewReplyRequest; +import org.wooriverygood.api.comment.repository.CommentRepository; +import org.wooriverygood.api.post.domain.Post; +import org.wooriverygood.api.post.domain.PostCategory; +import org.wooriverygood.api.post.repository.PostRepository; +import org.wooriverygood.api.util.MockTest; + +import java.util.ArrayList; +import java.util.Optional; + +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.anyLong; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class CommentCreateServiceTest extends MockTest { + + @InjectMocks + private CommentCreateService commentCreateService; + + @Mock + private PostRepository postRepository; + + @Mock + private CommentRepository commentRepository; + + private Post post = Post.builder() + .id(6L) + .category(PostCategory.OFFER) + .title("title6") + .content("content6") + .author("author6") + .comments(new ArrayList<>()) + .postLikes(new ArrayList<>()) + .build(); + + private Comment comment; + + private Comment reply; + + + @BeforeEach + void setUp() { + comment = Comment.builder() + .post(post) + .build(); + reply = Comment.builder() + .parent(comment) + .build(); + } + + @Test + @DisplayName("특정 게시글의 댓글을 작성한다.") + void addComment() { + NewCommentRequest newCommentRequest = NewCommentRequest.builder() + .content("comment content") + .build(); + + when(commentRepository.save(any(Comment.class))) + .thenReturn(Comment.builder() + .author(authInfo.getUsername()) + .content(newCommentRequest.getContent()) + .build()); + + when(postRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(post)); + + commentCreateService.addComment(authInfo, 1L, newCommentRequest); + + verify(commentRepository).save(any(Comment.class)); + } + + @Test + @DisplayName("특정 댓글의 대댓글을 작성한다.") + void addReply() { + NewReplyRequest request = NewReplyRequest.builder() + .content("reply content") + .build(); + + when(commentRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(comment)); + + commentCreateService.addReply(1L, request, authInfo); + Comment reply = comment.getChildren().get(0); + + assertThat(reply.getContent()).isEqualTo(request.getContent()); + assertThat(reply.getParent()).isEqualTo(comment); + } + + @Test + @DisplayName("대댓글의 대댓글을 작성할 수 없다.") + void addReply_exception_depth() { + NewReplyRequest request = NewReplyRequest.builder() + .content("reply content") + .build(); + + when(commentRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(reply)); + + assertThatThrownBy(() -> commentCreateService.addReply(1L, request, authInfo)) + .isInstanceOf(ReplyDepthException.class); + } + +} \ No newline at end of file diff --git a/src/test/java/org/wooriverygood/api/comment/application/CommentDeleteServiceTest.java b/src/test/java/org/wooriverygood/api/comment/application/CommentDeleteServiceTest.java new file mode 100644 index 0000000..1e02536 --- /dev/null +++ b/src/test/java/org/wooriverygood/api/comment/application/CommentDeleteServiceTest.java @@ -0,0 +1,132 @@ +package org.wooriverygood.api.comment.application; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.wooriverygood.api.comment.domain.Comment; +import org.wooriverygood.api.comment.repository.CommentLikeRepository; +import org.wooriverygood.api.comment.repository.CommentRepository; +import org.wooriverygood.api.global.error.exception.AuthorizationException; +import org.wooriverygood.api.util.MockTest; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +class CommentDeleteServiceTest extends MockTest { + + @InjectMocks + private CommentDeleteService commentDeleteService; + + @Mock + private CommentRepository commentRepository; + + @Mock + private CommentLikeRepository commentLikeRepository; + + @Mock + private Comment comment; + + @Mock + private Comment reply; + + + @Test + @DisplayName("권한이 있는 댓글을 삭제한다.") + void deleteComment() { + when(commentRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(comment)); + when(comment.isParent()) + .thenReturn(true); + when(comment.hasNoReply()) + .thenReturn(true); + + commentDeleteService.deleteComment(comment.getId(), authInfo); + + verify(comment).validateAuthor(authInfo.getUsername()); + verify(commentLikeRepository).deleteAllByComment(comment); + verify(commentRepository).delete(comment); + } + + @Test + @DisplayName("권한이 있는 대댓글을 삭제한다.") + void deleteReply() { + when(commentRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(reply)); + when(reply.isParent()) + .thenReturn(false); + when(reply.getParent()) + .thenReturn(comment); + commentDeleteService.deleteComment(reply.getId(), authInfo); + + verify(reply).validateAuthor(authInfo.getUsername()); + verify(commentLikeRepository).deleteAllByComment(reply); + verify(comment).deleteChild(reply); + verify(commentRepository).delete(reply); + } + + @Test + @DisplayName("권한이 없는 대댓글을 삭제할 수 없다.") + void deleteReply_exception_noAuth() { + when(commentRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(reply)); + doThrow(new AuthorizationException()) + .when(reply) + .validateAuthor(anyString()); + + assertThatThrownBy(() -> commentDeleteService.deleteComment(reply.getId(), authInfo)) + .isInstanceOf(AuthorizationException.class); + } + + @Test + @DisplayName("부모 댓글을 삭제해도 대댓글은 남아있다.") + void deleteComment_keepChildren() { + when(commentRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(comment)); + when(comment.isParent()) + .thenReturn(true); + when(comment.hasNoReply()) + .thenReturn(false); + + commentDeleteService.deleteComment(comment.getId(), authInfo); + + verify(comment).willBeDeleted(); + } + + @Test + @DisplayName("특정 대댓글 삭제 후, 삭제 예정으로 처리되고 대댓글이 없는 부모 댓글을 삭제한다.") + void deletePrentAndReply() { + when(commentRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(reply)); + when(reply.isParent()) + .thenReturn(false); + when(reply.getParent()) + .thenReturn(comment); + when(comment.canDelete()) + .thenReturn(true); + + commentDeleteService.deleteComment(reply.getId(), authInfo); + + verify(comment).deleteChild(reply); + verify(commentRepository).delete(reply); + verify(commentRepository).delete(comment); + } + + @Test + @DisplayName("권한이 없는 댓글은 삭제할 수 없다") + void deleteComment_exception_noAuth() { + when(commentRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(comment)); + doThrow(new AuthorizationException()) + .when(comment) + .validateAuthor(anyString()); + + assertThatThrownBy(() -> commentDeleteService.deleteComment(comment.getId(), authInfo)) + .isInstanceOf(AuthorizationException.class); + } + +} \ No newline at end of file diff --git a/src/test/java/org/wooriverygood/api/comment/application/CommentFindServiceTest.java b/src/test/java/org/wooriverygood/api/comment/application/CommentFindServiceTest.java new file mode 100644 index 0000000..762f447 --- /dev/null +++ b/src/test/java/org/wooriverygood/api/comment/application/CommentFindServiceTest.java @@ -0,0 +1,146 @@ +package org.wooriverygood.api.comment.application; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.wooriverygood.api.comment.domain.Comment; +import org.wooriverygood.api.comment.dto.CommentsResponse; +import org.wooriverygood.api.comment.repository.CommentLikeRepository; +import org.wooriverygood.api.comment.repository.CommentRepository; +import org.wooriverygood.api.post.domain.Post; +import org.wooriverygood.api.post.domain.PostCategory; +import org.wooriverygood.api.util.MockTest; + +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.when; + +class CommentFindServiceTest extends MockTest { + + @InjectMocks + private CommentFindService commentFindService; + + @Mock + private CommentRepository commentRepository; + + @Mock + private CommentLikeRepository commentLikeRepository; + + private final int COMMENT_COUNT = 10; + + private List comments = new ArrayList<>(); + + private Post post = Post.builder() + .id(6L) + .category(PostCategory.OFFER) + .title("title6") + .content("content6") + .author("author6") + .comments(new ArrayList<>()) + .postLikes(new ArrayList<>()) + .build(); + + @BeforeEach + void setUp() { + for (int i = 0; i < COMMENT_COUNT; i++) { + Comment comment = Comment.builder() + .id((long) i) + .content("comment" + i) + .author("author" + i) + .post(post) + .build(); + comments.add(comment); + } + } + + @Test + @DisplayName("유효한 id를 통해 특정 게시글의 댓글들을 불러온다.") + void findAllCommentsByPostId() { + when(commentRepository.findAllByPostId(anyLong())) + .thenReturn(comments); + when(commentLikeRepository.existsByCommentAndUsername(any(Comment.class), anyString())) + .thenReturn(true); + + CommentsResponse response = commentFindService.findAllCommentsByPostId(2L, authInfo); + + assertThat(response.getComments().size()).isEqualTo(COMMENT_COUNT); + } + + @Test + @DisplayName("삭제 처리될 댓글의 내용은 비어있다.") + void findAllCommentsByPostId_softRemoved() { + comments = new ArrayList<>(); + comments.add(Comment.builder() + .id(1L) + .content("Content") + .author("Author") + .post(post) + .softRemoved(true) + .build()); + when(commentRepository.findAllByPostId(anyLong())) + .thenReturn(comments); + + CommentsResponse response = commentFindService.findAllCommentsByPostId(2L, authInfo); + + assertThat(response.getComments().get(0).getCommentContent()).isEqualTo(null); + } + + @Test + @DisplayName("대댓글만 불러올 수 없다.") + void findAllCommentsByPostId_cannot_only_reply() { + Comment comment = Comment.builder() + .id(1L) + .content("Parent") + .author("author1") + .post(post) + .build(); + comments = new ArrayList<>(); + comments.add(Comment.builder() + .id(2L) + .content("Child") + .author("Author2") + .post(post) + .parent(comment) + .build()); + when(commentRepository.findAllByPostId(anyLong())) + .thenReturn(comments); + + CommentsResponse response = commentFindService.findAllCommentsByPostId(2L, authInfo); + + assertThat(response.getComments().size()).isEqualTo(0); + } + + @Test + @DisplayName("댓글과 대댓글들을 모두 불러온다.") + void findAllCommentsByPostId_withReplies() { + comments = new ArrayList<>(); + Comment comment = Comment.builder() + .id(1L) + .content("Parent") + .author("author1") + .post(post) + .build(); + comments.add(comment); + Comment reply = Comment.builder() + .id(2L) + .content("Child") + .author("Author2") + .post(post) + .parent(comment) + .build(); + comments.add(reply); + comment.addChildren(reply); + when(commentRepository.findAllByPostId(anyLong())) + .thenReturn(comments); + + CommentsResponse response = commentFindService.findAllCommentsByPostId(2L, authInfo); + + assertThat(response.getComments().get(0).getReplies().size()).isEqualTo(1); + } + +} \ No newline at end of file diff --git a/src/test/java/org/wooriverygood/api/comment/application/CommentLikeToggleServiceTest.java b/src/test/java/org/wooriverygood/api/comment/application/CommentLikeToggleServiceTest.java new file mode 100644 index 0000000..d20e806 --- /dev/null +++ b/src/test/java/org/wooriverygood/api/comment/application/CommentLikeToggleServiceTest.java @@ -0,0 +1,114 @@ +package org.wooriverygood.api.comment.application; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.wooriverygood.api.comment.domain.Comment; +import org.wooriverygood.api.comment.domain.CommentLike; +import org.wooriverygood.api.comment.dto.*; +import org.wooriverygood.api.comment.repository.CommentLikeRepository; +import org.wooriverygood.api.comment.repository.CommentRepository; +import org.wooriverygood.api.post.domain.Post; +import org.wooriverygood.api.post.domain.PostCategory; +import org.wooriverygood.api.util.MockTest; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.when; + +class CommentLikeToggleServiceTest extends MockTest { + + @InjectMocks + private CommentLikeToggleService commentLikeToggleService; + + @Mock + private CommentRepository commentRepository; + + @Mock + private CommentLikeRepository commentLikeRepository; + + private final int COMMENT_COUNT = 10; + + List comments = new ArrayList<>(); + + private Post post = Post.builder() + .id(6L) + .category(PostCategory.OFFER) + .title("title6") + .content("content6") + .author("author6") + .comments(new ArrayList<>()) + .postLikes(new ArrayList<>()) + .build(); + + private Comment comment = Comment.builder() + .id(2L) + .post(post) + .content("comment content") + .author(authInfo.getUsername()) + .commentLikes(new ArrayList<>()) + .build(); + + private Comment reply = Comment.builder() + .id(3L) + .post(post) + .content("reply content") + .author(authInfo.getUsername()) + .commentLikes(new ArrayList<>()) + .parent(comment) + .build(); + + + @BeforeEach + void setUpPosts() { + comment.getChildren().add(reply); + + for (int i = 0; i < COMMENT_COUNT; i++) { + Comment comment = Comment.builder() + .id((long) i) + .content("comment" + i) + .author("author" + i) + .post(post) + .build(); + comments.add(comment); + } + } + + @Test + @DisplayName("특정 댓글의 좋아요를 1 올린다.") + void likeComment_up() { + when(commentRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(comment)); + + CommentLikeResponse response = commentLikeToggleService.likeComment(comment.getId(), authInfo); + + Assertions.assertThat(response.getLikeCount()).isEqualTo(comment.getLikeCount() + 1); + Assertions.assertThat(response.isLiked()).isEqualTo(true); + } + + @Test + @DisplayName("특정 댓글의 좋아요를 1 내린다.") + void likeComment_down() { + CommentLike commentLike = CommentLike.builder() + .id(2L) + .comment(comment) + .username(authInfo.getUsername()) + .build(); + when(commentRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(comment)); + when(commentLikeRepository.findByCommentAndUsername(any(Comment.class), anyString())) + .thenReturn(Optional.ofNullable(commentLike)); + + CommentLikeResponse response = commentLikeToggleService.likeComment(comment.getId(), authInfo); + + Assertions.assertThat(response.getLikeCount()).isEqualTo(comment.getLikeCount() - 1); + Assertions.assertThat(response.isLiked()).isEqualTo(false); + } + +} \ No newline at end of file diff --git a/src/test/java/org/wooriverygood/api/comment/application/CommentUpdateServiceTest.java b/src/test/java/org/wooriverygood/api/comment/application/CommentUpdateServiceTest.java new file mode 100644 index 0000000..5121407 --- /dev/null +++ b/src/test/java/org/wooriverygood/api/comment/application/CommentUpdateServiceTest.java @@ -0,0 +1,91 @@ +package org.wooriverygood.api.comment.application; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.wooriverygood.api.comment.domain.Comment; +import org.wooriverygood.api.comment.dto.CommentUpdateRequest; +import org.wooriverygood.api.comment.repository.CommentRepository; +import org.wooriverygood.api.global.auth.AuthInfo; +import org.wooriverygood.api.global.error.exception.AuthorizationException; +import org.wooriverygood.api.post.domain.Post; +import org.wooriverygood.api.post.domain.PostCategory; +import org.wooriverygood.api.util.MockTest; + +import java.util.ArrayList; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.when; + +class CommentUpdateServiceTest extends MockTest { + + @InjectMocks + private CommentUpdateService commentUpdateService; + + @Mock + private CommentRepository commentRepository; + + private Post post = Post.builder() + .id(6L) + .category(PostCategory.OFFER) + .title("title6") + .content("content6") + .author("author6") + .comments(new ArrayList<>()) + .postLikes(new ArrayList<>()) + .build(); + + private Comment comment; + + + @BeforeEach + void setUp() { + comment = Comment.builder() + .id(2L) + .post(post) + .content("comment content") + .author(authInfo.getUsername()) + .commentLikes(new ArrayList<>()) + .build(); + } + + @Test + @DisplayName("권한이 있는 댓글을 수정한다.") + void updateComment() { + CommentUpdateRequest request = CommentUpdateRequest.builder() + .content("new comment content") + .build(); + + when(commentRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(comment)); + + commentUpdateService.updateComment(comment.getId(), request, authInfo); + + assertThat(comment.isUpdated()).isEqualTo(true); + assertThat(comment.getContent()).isEqualTo(request.getContent()); + } + + @Test + @DisplayName("권한이 없는 댓글을 수정할 수 없다.") + void updateComment_exception_noAuth() { + CommentUpdateRequest request = CommentUpdateRequest.builder() + .content("new comment content") + .build(); + AuthInfo noAuthInfo = AuthInfo.builder() + .sub("no") + .username("no") + .build(); + + when(commentRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(comment)); + + assertThatThrownBy(() -> commentUpdateService.updateComment(comment.getId(), request, noAuthInfo)) + .isInstanceOf(AuthorizationException.class); + } + +} \ No newline at end of file diff --git a/src/test/java/org/wooriverygood/api/comment/service/CommentServiceTest.java b/src/test/java/org/wooriverygood/api/comment/service/CommentServiceTest.java deleted file mode 100644 index cbe96d4..0000000 --- a/src/test/java/org/wooriverygood/api/comment/service/CommentServiceTest.java +++ /dev/null @@ -1,303 +0,0 @@ -package org.wooriverygood.api.comment.service; - -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -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.Mockito; -import org.mockito.junit.jupiter.MockitoExtension; -import org.wooriverygood.api.global.error.exception.AuthorizationException; -import org.wooriverygood.api.advice.exception.ReplyDepthException; -import org.wooriverygood.api.comment.domain.Comment; -import org.wooriverygood.api.comment.domain.CommentLike; -import org.wooriverygood.api.comment.dto.*; -import org.wooriverygood.api.comment.repository.CommentLikeRepository; -import org.wooriverygood.api.comment.repository.CommentRepository; -import org.wooriverygood.api.post.domain.Post; -import org.wooriverygood.api.post.domain.PostCategory; -import org.wooriverygood.api.post.repository.PostRepository; -import org.wooriverygood.api.global.auth.AuthInfo; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import static org.mockito.ArgumentMatchers.any; - -@ExtendWith(MockitoExtension.class) -class CommentServiceTest { - - @InjectMocks - private CommentService commentService; - - @Mock - private CommentRepository commentRepository; - - @Mock - private PostRepository postRepository; - - @Mock - private CommentLikeRepository commentLikeRepository; - - private final int COMMENT_COUNT = 10; - - List comments = new ArrayList<>(); - - Post singlePost = Post.builder() - .id(6L) - .category(PostCategory.OFFER) - .title("title6") - .content("content6") - .author("author6") - .comments(new ArrayList<>()) - .postLikes(new ArrayList<>()) - .build(); - - AuthInfo authInfo = AuthInfo.builder() - .sub("22222-34534-123") - .username("22222-34534-123") - .build(); - - Comment singleComment = Comment.builder() - .id(2L) - .post(singlePost) - .content("comment content") - .author(authInfo.getUsername()) - .commentLikes(new ArrayList<>()) - .build(); - - Comment reply = Comment.builder() - .id(3L) - .post(singlePost) - .content("reply content") - .author(authInfo.getUsername()) - .commentLikes(new ArrayList<>()) - .parent(singleComment) - .build(); - - - @BeforeEach - void setUpPosts() { - singleComment.getChildren().add(reply); - - for (int i = 0; i < COMMENT_COUNT; i++) { - Comment comment = Comment.builder() - .id((long) i) - .content("comment" + i) - .author("author" + i) - .post(singlePost) - .build(); - comments.add(comment); - } - } - - @Test - @DisplayName("유효한 id를 통해 특정 게시글의 댓글들을 불러온다.") - void findAllCommentsByPostId() { - Mockito.when(commentRepository.findAllByPostId(any())) - .thenReturn(comments); - - List responses = commentService.findAllComments(2L, authInfo); - - Assertions.assertThat(responses.size()).isEqualTo(COMMENT_COUNT); - } - - @Test - @DisplayName("특정 게시글의 댓글을 작성한다.") - void addComment() { - NewCommentRequest newCommentRequest = NewCommentRequest.builder() - .content("comment content") - .build(); - - Mockito.when(commentRepository.save(any(Comment.class))) - .thenReturn(Comment.builder() - .author(authInfo.getUsername()) - .content(newCommentRequest.getContent()) - .build()); - - Mockito.when(postRepository.findById(any())) - .thenReturn(Optional.ofNullable(singlePost)); - - NewCommentResponse response = commentService.addComment(authInfo, 1L, newCommentRequest); - - Assertions.assertThat(response.getAuthor()).isEqualTo(authInfo.getUsername()); - Assertions.assertThat(response.getContent()).isEqualTo(newCommentRequest.getContent()); - } - - @Test - @DisplayName("특정 댓글의 좋아요를 1 올린다.") - void likeComment_up() { - Mockito.when(commentRepository.findById(any(Long.class))) - .thenReturn(Optional.ofNullable(singleComment)); - - CommentLikeResponse response = commentService.likeComment(singleComment.getId(), authInfo); - - Assertions.assertThat(response.getLike_count()).isEqualTo(singleComment.getLikeCount() + 1); - Assertions.assertThat(response.isLiked()).isEqualTo(true); - } - - @Test - @DisplayName("특정 댓글의 좋아요를 1 내린다.") - void likeComment_down() { - CommentLike commentLike = CommentLike.builder() - .id(2L) - .comment(singleComment) - .username(authInfo.getUsername()) - .build(); - Mockito.when(commentRepository.findById(any(Long.class))) - .thenReturn(Optional.ofNullable(singleComment)); - Mockito.when(commentLikeRepository.findByCommentAndUsername(any(Comment.class), any(String.class))) - .thenReturn(Optional.ofNullable(commentLike)); - - CommentLikeResponse response = commentService.likeComment(singleComment.getId(), authInfo); - - Assertions.assertThat(response.getLike_count()).isEqualTo(singleComment.getLikeCount() - 1); - Assertions.assertThat(response.isLiked()).isEqualTo(false); - } - - @Test - @DisplayName("권한이 있는 댓글을 수정한다.") - void updateComment() { - CommentUpdateRequest request = CommentUpdateRequest.builder() - .content("new comment content") - .build(); - - Mockito.when(commentRepository.findById(any(Long.class))) - .thenReturn(Optional.ofNullable(singleComment)); - - CommentUpdateResponse response = commentService.updateComment(singleComment.getId(), request, authInfo); - - Assertions.assertThat(response.getComment_id()).isEqualTo(singleComment.getId()); - Assertions.assertThat(singleComment.isUpdated()).isEqualTo(true); - } - - @Test - @DisplayName("권한이 없는 댓글을 수정할 수 없다.") - void updateComment_exception_noAuth() { - CommentUpdateRequest request = CommentUpdateRequest.builder() - .content("new comment content") - .build(); - AuthInfo noAuthInfo = AuthInfo.builder() - .sub("no") - .username("no") - .build(); - - Mockito.when(commentRepository.findById(any(Long.class))) - .thenReturn(Optional.ofNullable(singleComment)); - - Assertions.assertThatThrownBy(() -> commentService.updateComment(singleComment.getId(), request, noAuthInfo)) - .isInstanceOf(AuthorizationException.class); - } - - @Test - @DisplayName("권한이 있는 댓글을 삭제한다.") - void deleteComment() { - Mockito.when(commentRepository.findById(any(Long.class))) - .thenReturn(Optional.ofNullable(singleComment)); - - CommentDeleteResponse response = commentService.deleteComment(singleComment.getId(), authInfo); - - Assertions.assertThat(response.getComment_id()).isEqualTo(singleComment.getId()); - } - - @Test - @DisplayName("권한이 없는 댓글은 삭제할 수 없다") - void deleteComment_exception_noAuth() { - Mockito.when(commentRepository.findById(any(Long.class))) - .thenReturn(Optional.ofNullable(singleComment)); - - AuthInfo noAuthInfo = AuthInfo.builder() - .sub("no") - .username("no") - .build(); - - Assertions.assertThatThrownBy(() -> commentService.deleteComment(singleComment.getId(), noAuthInfo)) - .isInstanceOf(AuthorizationException.class); - } - - @Test - @DisplayName("특정 댓글의 대댓글을 작성한다.") - void addReply() { - NewReplyRequest request = NewReplyRequest.builder() - .content("reply content") - .build(); - - Mockito.when(commentRepository.findById(any(Long.class))) - .thenReturn(Optional.ofNullable(singleComment)); - - commentService.addReply(singleComment.getId(), request, authInfo); - Comment reply = singleComment.getChildren().get(0); - - Assertions.assertThat(reply.getContent()).isEqualTo(request.getContent()); - Assertions.assertThat(reply.getParent()).isEqualTo(singleComment); - } - - @Test - @DisplayName("대댓글의 대댓글을 작성할 수 없다.") - void addReply_exception_depth() { - NewReplyRequest request = NewReplyRequest.builder() - .content("reply content") - .build(); - - Mockito.when(commentRepository.findById(any(Long.class))) - .thenReturn(Optional.ofNullable(reply)); - - Assertions.assertThatThrownBy(() -> commentService.addReply(reply.getId(), request, authInfo)) - .isInstanceOf(ReplyDepthException.class); - } - - @Test - @DisplayName("권한이 있는 대댓글을 삭제한다.") - void deleteReply() { - Mockito.when(commentRepository.findById(any(Long.class))) - .thenReturn(Optional.ofNullable(reply)); - - CommentDeleteResponse response = commentService.deleteComment(reply.getId(), authInfo); - - Assertions.assertThat(response.getComment_id()).isEqualTo(reply.getId()); - Assertions.assertThat(singleComment.getChildren().size()).isEqualTo(0); - } - - @Test - @DisplayName("권한이 없는 대댓글을 삭제할 수 없다.") - void deleteReply_exception_noAuth() { - Mockito.when(commentRepository.findById(any(Long.class))) - .thenReturn(Optional.ofNullable(reply)); - - AuthInfo noAuthInfo = AuthInfo.builder() - .sub("no") - .username("no") - .build(); - - Assertions.assertThatThrownBy(() -> commentService.deleteComment(reply.getId(), noAuthInfo)) - .isInstanceOf(AuthorizationException.class); - } - - @Test - @DisplayName("부모 댓글을 삭제해도 대댓글은 남아있다.") - void deleteComment_keepChildren() { - Mockito.when(commentRepository.findById(any(Long.class))) - .thenReturn(Optional.ofNullable(singleComment)); - - commentService.deleteComment(singleComment.getId(), authInfo); - - Assertions.assertThat(singleComment.isSoftRemoved()).isEqualTo(true); - Assertions.assertThat(singleComment.getChildren().size()).isEqualTo(1); - } - - @Test - @DisplayName("특정 대댓글 삭제 후, 삭제 예정으로 처리되고 대댓글이 없는 부모 댓글을 삭제한다.") - void deletePrentAndReply() { - singleComment.willBeDeleted(); - Mockito.when(commentRepository.findById(any(Long.class))) - .thenReturn(Optional.ofNullable(reply)); - - commentService.deleteComment(reply.getId(), authInfo); - - Assertions.assertThat(singleComment.canDelete()).isEqualTo(true); - } - -} \ No newline at end of file diff --git a/src/test/java/org/wooriverygood/api/util/ApiTest.java b/src/test/java/org/wooriverygood/api/util/ApiTest.java index a848eb7..519bf5e 100644 --- a/src/test/java/org/wooriverygood/api/util/ApiTest.java +++ b/src/test/java/org/wooriverygood/api/util/ApiTest.java @@ -11,8 +11,8 @@ import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; -import org.wooriverygood.api.comment.controller.CommentController; -import org.wooriverygood.api.comment.service.CommentService; +import org.wooriverygood.api.comment.api.CommentApi; +import org.wooriverygood.api.comment.application.*; import org.wooriverygood.api.course.application.CourseFindService; import org.wooriverygood.api.post.application.*; import org.wooriverygood.api.report.api.ReportApi; @@ -34,7 +34,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; @WebMvcTest({ - CommentController.class, + CommentApi.class, PostApi.class, CourseApi.class, ReviewApi.class, @@ -71,7 +71,19 @@ public class ApiTest { protected ReviewValidateAccessService reviewValidateAccessService; @MockBean - protected CommentService commentService; + protected CommentCreateService commentCreateService; + + @MockBean + protected CommentDeleteService commentDeleteService; + + @MockBean + protected CommentFindService commentFindService; + + @MockBean + protected CommentUpdateService commentUpdateService; + + @MockBean + protected CommentLikeToggleService commentLikeToggleService; @MockBean protected PostLikeToggleService postLikeToggleService; diff --git a/src/test/java/org/wooriverygood/api/util/MockTest.java b/src/test/java/org/wooriverygood/api/util/MockTest.java index 329eec3..e8ee391 100644 --- a/src/test/java/org/wooriverygood/api/util/MockTest.java +++ b/src/test/java/org/wooriverygood/api/util/MockTest.java @@ -2,8 +2,14 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; +import org.wooriverygood.api.global.auth.AuthInfo; @ExtendWith(MockitoExtension.class) public class MockTest { + protected AuthInfo authInfo = AuthInfo.builder() + .sub("22222-34534-123") + .username("22222-34534-123") + .build(); + } From 90afb9fb90a5c182161d6ec04fb6915e6c592fe5 Mon Sep 17 00:00:00 2001 From: Park Geonho Date: Tue, 2 Apr 2024 22:49:43 +0800 Subject: [PATCH 15/21] =?UTF-8?q?[docs]=20=EB=8C=93=EA=B8=80=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=98=88=EC=99=B8=20=ED=81=B4=EB=9E=98=EC=8A=A4=20?= =?UTF-8?q?=ED=8C=A8=ED=82=A4=EC=A7=80=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/comment/application/CommentCreateService.java | 4 ++-- .../api/comment/application/CommentDeleteService.java | 2 +- .../api/comment/application/CommentLikeToggleService.java | 3 +-- .../api/comment/application/CommentUpdateService.java | 2 +- .../exception/CommentNotFoundException.java | 2 +- .../{advice => comment}/exception/ReplyDepthException.java | 2 +- .../api/report/application/CommentReportService.java | 2 +- .../org/wooriverygood/api/comment/api/CommentApiTest.java | 2 +- .../api/comment/application/CommentCreateServiceTest.java | 2 +- .../api/report/application/CommentReportServiceTest.java | 2 +- 10 files changed, 11 insertions(+), 12 deletions(-) rename src/main/java/org/wooriverygood/api/{advice => comment}/exception/CommentNotFoundException.java (85%) rename src/main/java/org/wooriverygood/api/{advice => comment}/exception/ReplyDepthException.java (85%) diff --git a/src/main/java/org/wooriverygood/api/comment/application/CommentCreateService.java b/src/main/java/org/wooriverygood/api/comment/application/CommentCreateService.java index 2071396..5214f8e 100644 --- a/src/main/java/org/wooriverygood/api/comment/application/CommentCreateService.java +++ b/src/main/java/org/wooriverygood/api/comment/application/CommentCreateService.java @@ -3,8 +3,8 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.wooriverygood.api.advice.exception.CommentNotFoundException; -import org.wooriverygood.api.advice.exception.ReplyDepthException; +import org.wooriverygood.api.comment.exception.CommentNotFoundException; +import org.wooriverygood.api.comment.exception.ReplyDepthException; import org.wooriverygood.api.comment.domain.Comment; import org.wooriverygood.api.comment.dto.NewCommentRequest; import org.wooriverygood.api.comment.dto.NewReplyRequest; diff --git a/src/main/java/org/wooriverygood/api/comment/application/CommentDeleteService.java b/src/main/java/org/wooriverygood/api/comment/application/CommentDeleteService.java index 1407520..9b8f2de 100644 --- a/src/main/java/org/wooriverygood/api/comment/application/CommentDeleteService.java +++ b/src/main/java/org/wooriverygood/api/comment/application/CommentDeleteService.java @@ -3,7 +3,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.wooriverygood.api.advice.exception.CommentNotFoundException; +import org.wooriverygood.api.comment.exception.CommentNotFoundException; import org.wooriverygood.api.comment.domain.Comment; import org.wooriverygood.api.comment.repository.CommentLikeRepository; import org.wooriverygood.api.comment.repository.CommentRepository; diff --git a/src/main/java/org/wooriverygood/api/comment/application/CommentLikeToggleService.java b/src/main/java/org/wooriverygood/api/comment/application/CommentLikeToggleService.java index 2bc379b..9abd67d 100644 --- a/src/main/java/org/wooriverygood/api/comment/application/CommentLikeToggleService.java +++ b/src/main/java/org/wooriverygood/api/comment/application/CommentLikeToggleService.java @@ -8,7 +8,7 @@ import org.wooriverygood.api.comment.dto.*; import org.wooriverygood.api.comment.repository.CommentLikeRepository; import org.wooriverygood.api.comment.repository.CommentRepository; -import org.wooriverygood.api.advice.exception.CommentNotFoundException; +import org.wooriverygood.api.comment.exception.CommentNotFoundException; import org.wooriverygood.api.global.auth.AuthInfo; import java.util.Optional; @@ -18,7 +18,6 @@ @RequiredArgsConstructor public class CommentLikeToggleService { - private final CommentRepository commentRepository; private final CommentLikeRepository commentLikeRepository; diff --git a/src/main/java/org/wooriverygood/api/comment/application/CommentUpdateService.java b/src/main/java/org/wooriverygood/api/comment/application/CommentUpdateService.java index b2faea0..6ddca9e 100644 --- a/src/main/java/org/wooriverygood/api/comment/application/CommentUpdateService.java +++ b/src/main/java/org/wooriverygood/api/comment/application/CommentUpdateService.java @@ -3,7 +3,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.wooriverygood.api.advice.exception.CommentNotFoundException; +import org.wooriverygood.api.comment.exception.CommentNotFoundException; import org.wooriverygood.api.comment.domain.Comment; import org.wooriverygood.api.comment.dto.CommentUpdateRequest; import org.wooriverygood.api.comment.repository.CommentRepository; diff --git a/src/main/java/org/wooriverygood/api/advice/exception/CommentNotFoundException.java b/src/main/java/org/wooriverygood/api/comment/exception/CommentNotFoundException.java similarity index 85% rename from src/main/java/org/wooriverygood/api/advice/exception/CommentNotFoundException.java rename to src/main/java/org/wooriverygood/api/comment/exception/CommentNotFoundException.java index 91b5004..e45e533 100644 --- a/src/main/java/org/wooriverygood/api/advice/exception/CommentNotFoundException.java +++ b/src/main/java/org/wooriverygood/api/comment/exception/CommentNotFoundException.java @@ -1,4 +1,4 @@ -package org.wooriverygood.api.advice.exception; +package org.wooriverygood.api.comment.exception; import org.wooriverygood.api.global.error.exception.NotFoundException; diff --git a/src/main/java/org/wooriverygood/api/advice/exception/ReplyDepthException.java b/src/main/java/org/wooriverygood/api/comment/exception/ReplyDepthException.java similarity index 85% rename from src/main/java/org/wooriverygood/api/advice/exception/ReplyDepthException.java rename to src/main/java/org/wooriverygood/api/comment/exception/ReplyDepthException.java index ad49a33..90aee51 100644 --- a/src/main/java/org/wooriverygood/api/advice/exception/ReplyDepthException.java +++ b/src/main/java/org/wooriverygood/api/comment/exception/ReplyDepthException.java @@ -1,4 +1,4 @@ -package org.wooriverygood.api.advice.exception; +package org.wooriverygood.api.comment.exception; import org.wooriverygood.api.global.error.exception.BadRequestException; diff --git a/src/main/java/org/wooriverygood/api/report/application/CommentReportService.java b/src/main/java/org/wooriverygood/api/report/application/CommentReportService.java index 4a2cf5b..9b2f2bf 100644 --- a/src/main/java/org/wooriverygood/api/report/application/CommentReportService.java +++ b/src/main/java/org/wooriverygood/api/report/application/CommentReportService.java @@ -3,7 +3,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.wooriverygood.api.advice.exception.CommentNotFoundException; +import org.wooriverygood.api.comment.exception.CommentNotFoundException; import org.wooriverygood.api.report.exception.DuplicatedCommentReportException; import org.wooriverygood.api.comment.domain.Comment; import org.wooriverygood.api.comment.repository.CommentRepository; diff --git a/src/test/java/org/wooriverygood/api/comment/api/CommentApiTest.java b/src/test/java/org/wooriverygood/api/comment/api/CommentApiTest.java index bd554d9..afe7b04 100644 --- a/src/test/java/org/wooriverygood/api/comment/api/CommentApiTest.java +++ b/src/test/java/org/wooriverygood/api/comment/api/CommentApiTest.java @@ -6,7 +6,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.wooriverygood.api.global.error.exception.AuthorizationException; -import org.wooriverygood.api.advice.exception.ReplyDepthException; +import org.wooriverygood.api.comment.exception.ReplyDepthException; import org.wooriverygood.api.comment.dto.*; import org.wooriverygood.api.post.domain.Post; import org.wooriverygood.api.post.domain.PostCategory; diff --git a/src/test/java/org/wooriverygood/api/comment/application/CommentCreateServiceTest.java b/src/test/java/org/wooriverygood/api/comment/application/CommentCreateServiceTest.java index 7ed936b..d622f25 100644 --- a/src/test/java/org/wooriverygood/api/comment/application/CommentCreateServiceTest.java +++ b/src/test/java/org/wooriverygood/api/comment/application/CommentCreateServiceTest.java @@ -5,7 +5,7 @@ import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.wooriverygood.api.advice.exception.ReplyDepthException; +import org.wooriverygood.api.comment.exception.ReplyDepthException; import org.wooriverygood.api.comment.domain.Comment; import org.wooriverygood.api.comment.dto.NewCommentRequest; import org.wooriverygood.api.comment.dto.NewReplyRequest; diff --git a/src/test/java/org/wooriverygood/api/report/application/CommentReportServiceTest.java b/src/test/java/org/wooriverygood/api/report/application/CommentReportServiceTest.java index 5dae872..47988c3 100644 --- a/src/test/java/org/wooriverygood/api/report/application/CommentReportServiceTest.java +++ b/src/test/java/org/wooriverygood/api/report/application/CommentReportServiceTest.java @@ -4,7 +4,7 @@ import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.wooriverygood.api.advice.exception.CommentNotFoundException; +import org.wooriverygood.api.comment.exception.CommentNotFoundException; import org.wooriverygood.api.report.domain.CommentReport; import org.wooriverygood.api.report.exception.DuplicatedCommentReportException; import org.wooriverygood.api.comment.domain.Comment; From 06e4f809dc8a28b2f16dc0b36d318ba65bf032f4 Mon Sep 17 00:00:00 2001 From: Park Geonho Date: Sun, 28 Apr 2024 21:34:37 +0800 Subject: [PATCH 16/21] =?UTF-8?q?[fix]=20Review=EC=97=90=20Member=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/workspace.xml | 403 +++++++++--------- .../application/CommentCreateService.java | 2 +- .../application/CommentDeleteService.java | 2 +- .../application/CommentFindService.java | 2 +- .../api/comment/domain/Comment.java | 23 +- .../api/comment/domain/CommentLike.java | 1 - .../api/global/auth/AuthInfo.java | 6 +- ...thenticationPrincipalArgumentResolver.java | 17 +- .../api/global/config/WebConfig.java | 7 +- .../api/member/domain/Member.java | 56 +++ .../exception/MemberNotFoundException.java | 12 + .../member/repository/MemberRepository.java | 12 + .../application/ReviewCreateService.java | 13 +- .../application/ReviewDeleteService.java | 9 +- .../review/application/ReviewFindService.java | 19 +- .../application/ReviewLikeToggleService.java | 9 +- .../application/ReviewUpdateService.java | 11 +- .../ReviewValidateAccessService.java | 10 +- .../api/review/domain/Review.java | 27 +- .../repository/ReviewLikeRepository.java | 5 +- .../review/repository/ReviewRepository.java | 5 +- .../application/CommentCreateServiceTest.java | 6 +- .../application/CommentDeleteServiceTest.java | 4 +- .../application/CommentFindServiceTest.java | 2 +- .../CommentLikeToggleServiceTest.java | 2 +- .../application/ReviewCreateServiceTest.java | 16 +- .../application/ReviewDeleteServiceTest.java | 37 +- .../application/ReviewFindServiceTest.java | 27 +- .../ReviewLikeToggleServiceTest.java | 27 +- .../application/ReviewUpdateServiceTest.java | 43 +- .../ReviewValidateAccessServiceTest.java | 23 +- .../org/wooriverygood/api/util/MockTest.java | 4 + 32 files changed, 533 insertions(+), 309 deletions(-) create mode 100644 src/main/java/org/wooriverygood/api/member/domain/Member.java create mode 100644 src/main/java/org/wooriverygood/api/member/exception/MemberNotFoundException.java create mode 100644 src/main/java/org/wooriverygood/api/member/repository/MemberRepository.java diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 2273a1f..645a82f 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -4,26 +4,9 @@ - - - - - - - - - - - - - - - - - - - - + + + - - - + { + "selectedUrlAndAccountId": { + "url": "https://github.com/WooriVeryGood/honeycourses-backend-spring.git", + "accountId": "fa668461-63a5-431a-b8af-cda0dcb3c718" + } +} { "isMigrated": true } @@ -104,119 +85,136 @@ - - - - @@ -840,7 +852,11 @@ - @@ -854,6 +870,7 @@ + diff --git a/src/main/java/org/wooriverygood/api/comment/application/CommentCreateService.java b/src/main/java/org/wooriverygood/api/comment/application/CommentCreateService.java index 5214f8e..e28507c 100644 --- a/src/main/java/org/wooriverygood/api/comment/application/CommentCreateService.java +++ b/src/main/java/org/wooriverygood/api/comment/application/CommentCreateService.java @@ -50,7 +50,7 @@ public void addReply(Long commentId, NewReplyRequest request, AuthInfo authInfo) .post(parent.getPost()) .parent(parent) .build(); - parent.addChildren(child); + parent.addReply(child); commentRepository.save(child); } diff --git a/src/main/java/org/wooriverygood/api/comment/application/CommentDeleteService.java b/src/main/java/org/wooriverygood/api/comment/application/CommentDeleteService.java index 9b8f2de..826b8bd 100644 --- a/src/main/java/org/wooriverygood/api/comment/application/CommentDeleteService.java +++ b/src/main/java/org/wooriverygood/api/comment/application/CommentDeleteService.java @@ -47,7 +47,7 @@ private void deleteParent(Comment parent) { private void deleteChild(Comment reply) { Comment parent = reply.getParent(); - parent.deleteChild(reply); + parent.deleteReply(reply); commentRepository.delete(reply); if (parent.canDelete()) diff --git a/src/main/java/org/wooriverygood/api/comment/application/CommentFindService.java b/src/main/java/org/wooriverygood/api/comment/application/CommentFindService.java index a9c2a51..c6f09a0 100644 --- a/src/main/java/org/wooriverygood/api/comment/application/CommentFindService.java +++ b/src/main/java/org/wooriverygood/api/comment/application/CommentFindService.java @@ -44,7 +44,7 @@ private CommentResponse convertToCommentResponse(Comment comment, AuthInfo authI } private List convertToReplyResponses(Comment parent, AuthInfo authInfo) { - return parent.getChildren() + return parent.getReplies() .stream() .map(reply -> { boolean liked = commentLikeRepository.existsByCommentAndUsername(reply, authInfo.getUsername()); diff --git a/src/main/java/org/wooriverygood/api/comment/domain/Comment.java b/src/main/java/org/wooriverygood/api/comment/domain/Comment.java index 6c9d0c2..f87c8c8 100644 --- a/src/main/java/org/wooriverygood/api/comment/domain/Comment.java +++ b/src/main/java/org/wooriverygood/api/comment/domain/Comment.java @@ -6,6 +6,7 @@ import org.springframework.data.annotation.CreatedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; import org.wooriverygood.api.global.error.exception.AuthorizationException; +import org.wooriverygood.api.member.domain.Member; import org.wooriverygood.api.post.domain.Post; import org.wooriverygood.api.report.domain.CommentReport; @@ -15,8 +16,8 @@ import java.util.Objects; @Entity -@Table(name = "comments") @Getter +@Table(name = "comments") @EntityListeners(AuditingEntityListener.class) @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Comment { @@ -31,7 +32,7 @@ public class Comment { private Comment parent; @OneToMany(mappedBy = "parent") - private List children = new ArrayList<>(); + private List replies = new ArrayList<>(); @Column(name = "comment_content", length = 200, nullable = false) private String content; @@ -39,6 +40,9 @@ public class Comment { @Column(name = "comment_author", length = 1000) private String author; + @ManyToOne(fetch = FetchType.LAZY) + private Member member; + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "post_id", referencedColumnName = "post_id") private Post post; @@ -69,7 +73,9 @@ public class Comment { private boolean updated; @Builder - public Comment(Long id, String content, String author, Post post, Comment parent, List commentLikes, List reports, boolean softRemoved, boolean updated) { + public Comment(Long id, String content, String author, Post post, + Member member, Comment parent, List commentLikes, + List reports, boolean softRemoved, boolean updated) { this.id = id; this.content = content; this.author = author; @@ -79,6 +85,7 @@ public Comment(Long id, String content, String author, Post post, Comment parent this.reports = reports; this.softRemoved = softRemoved; this.updated = updated; + this.member = member; } public void addCommentLike(CommentLike commentLike) { @@ -110,12 +117,12 @@ public void updateContent(String content) { updated = true; } - public void addChildren(Comment reply) { - children.add(reply); + public void addReply(Comment reply) { + replies.add(reply); } - public void deleteChild(Comment reply) { - children.remove(reply); + public void deleteReply(Comment reply) { + replies.remove(reply); reply.delete(); } @@ -132,7 +139,7 @@ public boolean isReply() { } public boolean hasNoReply() { - return children.isEmpty(); + return replies.isEmpty(); } public void willBeDeleted() { diff --git a/src/main/java/org/wooriverygood/api/comment/domain/CommentLike.java b/src/main/java/org/wooriverygood/api/comment/domain/CommentLike.java index 320e161..7890f17 100644 --- a/src/main/java/org/wooriverygood/api/comment/domain/CommentLike.java +++ b/src/main/java/org/wooriverygood/api/comment/domain/CommentLike.java @@ -19,7 +19,6 @@ public class CommentLike { @JoinColumn(name = "comment_id", referencedColumnName = "comment_id") private Comment comment; - @Column private String username; diff --git a/src/main/java/org/wooriverygood/api/global/auth/AuthInfo.java b/src/main/java/org/wooriverygood/api/global/auth/AuthInfo.java index 8c56647..4aa4f01 100644 --- a/src/main/java/org/wooriverygood/api/global/auth/AuthInfo.java +++ b/src/main/java/org/wooriverygood/api/global/auth/AuthInfo.java @@ -6,11 +6,15 @@ @Getter public class AuthInfo { + private Long memberId; + private final String sub; + private final String username; @Builder - public AuthInfo(String sub, String username) { + public AuthInfo(Long memberId, String sub, String username) { + this.memberId = memberId; this.sub = sub; this.username = username; } diff --git a/src/main/java/org/wooriverygood/api/global/auth/AuthenticationPrincipalArgumentResolver.java b/src/main/java/org/wooriverygood/api/global/auth/AuthenticationPrincipalArgumentResolver.java index a3a1e77..c0d17da 100644 --- a/src/main/java/org/wooriverygood/api/global/auth/AuthenticationPrincipalArgumentResolver.java +++ b/src/main/java/org/wooriverygood/api/global/auth/AuthenticationPrincipalArgumentResolver.java @@ -1,5 +1,6 @@ package org.wooriverygood.api.global.auth; +import lombok.RequiredArgsConstructor; import org.springframework.core.MethodParameter; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; @@ -8,9 +9,15 @@ import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; +import org.wooriverygood.api.member.domain.Member; +import org.wooriverygood.api.member.repository.MemberRepository; +@RequiredArgsConstructor public class AuthenticationPrincipalArgumentResolver implements HandlerMethodArgumentResolver { + private final MemberRepository memberRepository; + + @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(Login.class); @@ -23,8 +30,9 @@ public Object resolveArgument(MethodParameter parameter, WebDataBinderFactory binderFactory) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication != null && authentication.getPrincipal() instanceof Jwt jwt) + if (authentication != null && authentication.getPrincipal() instanceof Jwt jwt) { return createAuthInfo(jwt); + } return AuthInfo.builder().build(); } @@ -33,7 +41,14 @@ private AuthInfo createAuthInfo(Jwt jwt) { String sub = jwt.getClaim("sub"); String username = jwt.getClaim("username"); + Member member = memberRepository.findByUsername(username) + .orElseGet(() -> { + Member newMember = Member.builder().username(username).build(); + return memberRepository.save(newMember); + }); + return AuthInfo.builder() + .memberId(member.getId()) .sub(sub) .username(username) .build(); diff --git a/src/main/java/org/wooriverygood/api/global/config/WebConfig.java b/src/main/java/org/wooriverygood/api/global/config/WebConfig.java index 40ddcb3..9278e3c 100644 --- a/src/main/java/org/wooriverygood/api/global/config/WebConfig.java +++ b/src/main/java/org/wooriverygood/api/global/config/WebConfig.java @@ -1,20 +1,25 @@ package org.wooriverygood.api.global.config; +import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.wooriverygood.api.global.auth.AuthenticationPrincipalArgumentResolver; +import org.wooriverygood.api.member.repository.MemberRepository; import java.util.List; @Configuration +@RequiredArgsConstructor public class WebConfig implements WebMvcConfigurer { + private final MemberRepository memberRepository; + @Bean public AuthenticationPrincipalArgumentResolver authenticationPrincipalArgumentResolver() { - return new AuthenticationPrincipalArgumentResolver(); + return new AuthenticationPrincipalArgumentResolver(memberRepository); } @Override diff --git a/src/main/java/org/wooriverygood/api/member/domain/Member.java b/src/main/java/org/wooriverygood/api/member/domain/Member.java new file mode 100644 index 0000000..6dbee95 --- /dev/null +++ b/src/main/java/org/wooriverygood/api/member/domain/Member.java @@ -0,0 +1,56 @@ +package org.wooriverygood.api.member.domain; + +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.wooriverygood.api.global.error.exception.AuthorizationException; +import org.wooriverygood.api.review.domain.Review; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +@Entity +@Getter +@Table(name = "members") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Member { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String username; + + @Column(name = "old_username") + private String oldUsername; + + private String email; + + @OneToMany(mappedBy = "member") + private List reviews = new ArrayList<>(); + + + @Builder + public Member(Long id, String username) { + this.id = id; + this.username = username; + } + + public boolean isSame(Member member) { + return Objects.equals(this.id, member.getId()); + } + + public void verify(Member member) { + if (!isSame(member)) { + throw new AuthorizationException(); + } + } + + public void addReview(Review review) { + reviews.add(review); + } + +} \ No newline at end of file diff --git a/src/main/java/org/wooriverygood/api/member/exception/MemberNotFoundException.java b/src/main/java/org/wooriverygood/api/member/exception/MemberNotFoundException.java new file mode 100644 index 0000000..7581fc4 --- /dev/null +++ b/src/main/java/org/wooriverygood/api/member/exception/MemberNotFoundException.java @@ -0,0 +1,12 @@ +package org.wooriverygood.api.member.exception; + +import org.wooriverygood.api.global.error.exception.NotFoundException; + +public class MemberNotFoundException extends NotFoundException { + private static final String MESSAGE = "유저 정보를 찾을 수 없습니다."; + + + public MemberNotFoundException() { + super(MESSAGE); + } +} diff --git a/src/main/java/org/wooriverygood/api/member/repository/MemberRepository.java b/src/main/java/org/wooriverygood/api/member/repository/MemberRepository.java new file mode 100644 index 0000000..d10e1df --- /dev/null +++ b/src/main/java/org/wooriverygood/api/member/repository/MemberRepository.java @@ -0,0 +1,12 @@ +package org.wooriverygood.api.member.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.wooriverygood.api.member.domain.Member; + +import java.util.Optional; + +public interface MemberRepository extends JpaRepository { + + Optional findByUsername(String username); + +} diff --git a/src/main/java/org/wooriverygood/api/review/application/ReviewCreateService.java b/src/main/java/org/wooriverygood/api/review/application/ReviewCreateService.java index 2804038..4c10231 100644 --- a/src/main/java/org/wooriverygood/api/review/application/ReviewCreateService.java +++ b/src/main/java/org/wooriverygood/api/review/application/ReviewCreateService.java @@ -7,6 +7,9 @@ import org.wooriverygood.api.course.domain.Course; import org.wooriverygood.api.course.repository.CourseRepository; import org.wooriverygood.api.global.auth.AuthInfo; +import org.wooriverygood.api.member.domain.Member; +import org.wooriverygood.api.member.exception.MemberNotFoundException; +import org.wooriverygood.api.member.repository.MemberRepository; import org.wooriverygood.api.review.domain.Review; import org.wooriverygood.api.review.dto.NewReviewRequest; import org.wooriverygood.api.review.repository.ReviewRepository; @@ -19,22 +22,28 @@ public class ReviewCreateService { private final ReviewRepository reviewRepository; + private final MemberRepository memberRepository; + @Transactional public void addReview(AuthInfo authInfo, Long courseId, NewReviewRequest request) { Course course = courseRepository.findById(courseId) .orElseThrow(CourseNotFoundException::new); - Review review = createReview(course, authInfo, request); + Member member = memberRepository.findById(authInfo.getMemberId()) + .orElseThrow(MemberNotFoundException::new); + Review review = createReview(course, authInfo, request, member); + member.addReview(review); reviewRepository.save(review); courseRepository.increaseReviewCount(review.getCourse().getId()); } - private Review createReview(Course course, AuthInfo authInfo, NewReviewRequest request) { + private Review createReview(Course course, AuthInfo authInfo, NewReviewRequest request, Member member) { return Review.builder() .reviewTitle(request.getReviewTitle()) .course(course) .instructorName(request.getInstructorName()) + .member(member) .takenSemyr(request.getTakenSemyr()) .reviewContent(request.getReviewContent()) .grade(request.getGrade()) diff --git a/src/main/java/org/wooriverygood/api/review/application/ReviewDeleteService.java b/src/main/java/org/wooriverygood/api/review/application/ReviewDeleteService.java index 19e5c19..8e1f122 100644 --- a/src/main/java/org/wooriverygood/api/review/application/ReviewDeleteService.java +++ b/src/main/java/org/wooriverygood/api/review/application/ReviewDeleteService.java @@ -5,6 +5,9 @@ import org.springframework.transaction.annotation.Transactional; import org.wooriverygood.api.course.repository.CourseRepository; import org.wooriverygood.api.global.auth.AuthInfo; +import org.wooriverygood.api.member.domain.Member; +import org.wooriverygood.api.member.exception.MemberNotFoundException; +import org.wooriverygood.api.member.repository.MemberRepository; import org.wooriverygood.api.review.domain.Review; import org.wooriverygood.api.review.exception.ReviewNotFoundException; import org.wooriverygood.api.review.repository.ReviewLikeRepository; @@ -20,12 +23,16 @@ public class ReviewDeleteService { private final ReviewLikeRepository reviewLikeRepository; + private final MemberRepository memberRepository; + @Transactional public void deleteReview(Long reviewId, AuthInfo authInfo) { Review review = reviewRepository.findById(reviewId) .orElseThrow(ReviewNotFoundException::new); - review.validateAuthor(authInfo.getUsername()); + Member member = memberRepository.findById(authInfo.getMemberId()) + .orElseThrow(MemberNotFoundException::new); + review.validateAuthor(member); reviewLikeRepository.deleteAllByReview(review); reviewRepository.delete(review); diff --git a/src/main/java/org/wooriverygood/api/review/application/ReviewFindService.java b/src/main/java/org/wooriverygood/api/review/application/ReviewFindService.java index d6cd6cb..3e51951 100644 --- a/src/main/java/org/wooriverygood/api/review/application/ReviewFindService.java +++ b/src/main/java/org/wooriverygood/api/review/application/ReviewFindService.java @@ -4,6 +4,9 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.wooriverygood.api.global.auth.AuthInfo; +import org.wooriverygood.api.member.domain.Member; +import org.wooriverygood.api.member.exception.MemberNotFoundException; +import org.wooriverygood.api.member.repository.MemberRepository; import org.wooriverygood.api.review.domain.Review; import org.wooriverygood.api.review.dto.ReviewResponse; import org.wooriverygood.api.review.dto.ReviewsResponse; @@ -13,33 +16,39 @@ import java.util.List; @Service -@Transactional(readOnly = true) @RequiredArgsConstructor +@Transactional(readOnly = true) public class ReviewFindService { private final ReviewRepository reviewRepository; private final ReviewLikeRepository reviewLikeRepository; + private final MemberRepository memberRepository; + public ReviewsResponse findAllReviewsByCourseId(Long courseId, AuthInfo authInfo) { List reviews = reviewRepository.findAllByCourseId(courseId); + Member member = memberRepository.findById(authInfo.getMemberId()) + .orElseThrow(MemberNotFoundException::new); return new ReviewsResponse(reviews.stream() .map(review -> { - boolean liked = reviewLikeRepository.existsByReviewAndUsername(review, authInfo.getUsername()); + boolean liked = reviewLikeRepository.existsByReviewAndMember(review, member); return ReviewResponse.of(review, - review.isSameAuthor(authInfo.getUsername()), + review.isSameAuthor(member), liked); }) .toList()); } public ReviewsResponse findMyReviews(AuthInfo authInfo) { - List reviews= reviewRepository.findByAuthorEmail(authInfo.getUsername()); + Member member = memberRepository.findById(authInfo.getMemberId()) + .orElseThrow(MemberNotFoundException::new); + List reviews= member.getReviews(); return new ReviewsResponse(reviews.stream() .map(review -> { - boolean liked = reviewLikeRepository.existsByReviewAndUsername(review, authInfo.getUsername()); + boolean liked = reviewLikeRepository.existsByReviewAndMember(review, member); return ReviewResponse.of(review, true, liked); }) .toList()); diff --git a/src/main/java/org/wooriverygood/api/review/application/ReviewLikeToggleService.java b/src/main/java/org/wooriverygood/api/review/application/ReviewLikeToggleService.java index 9128231..6159b42 100644 --- a/src/main/java/org/wooriverygood/api/review/application/ReviewLikeToggleService.java +++ b/src/main/java/org/wooriverygood/api/review/application/ReviewLikeToggleService.java @@ -3,6 +3,9 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.wooriverygood.api.member.domain.Member; +import org.wooriverygood.api.member.exception.MemberNotFoundException; +import org.wooriverygood.api.member.repository.MemberRepository; import org.wooriverygood.api.review.exception.ReviewNotFoundException; import org.wooriverygood.api.review.domain.Review; import org.wooriverygood.api.review.domain.ReviewLike; @@ -22,12 +25,16 @@ public class ReviewLikeToggleService { private final ReviewLikeRepository reviewLikeRepository; + private final MemberRepository memberRepository; + public ReviewLikeResponse toggleReviewLike(Long reviewId, AuthInfo authInfo) { Review review = reviewRepository.findById(reviewId) .orElseThrow(ReviewNotFoundException::new); + Member member = memberRepository.findById(authInfo.getMemberId()) + .orElseThrow(MemberNotFoundException::new); - Optional reviewLike = reviewLikeRepository.findByReviewAndUsername(review, authInfo.getUsername()); + Optional reviewLike = reviewLikeRepository.findByReviewAndMember(review, member); if (reviewLike.isEmpty()) { addReviewLike(review, authInfo.getUsername()); diff --git a/src/main/java/org/wooriverygood/api/review/application/ReviewUpdateService.java b/src/main/java/org/wooriverygood/api/review/application/ReviewUpdateService.java index 2234fc9..9472407 100644 --- a/src/main/java/org/wooriverygood/api/review/application/ReviewUpdateService.java +++ b/src/main/java/org/wooriverygood/api/review/application/ReviewUpdateService.java @@ -4,6 +4,9 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.wooriverygood.api.global.auth.AuthInfo; +import org.wooriverygood.api.member.domain.Member; +import org.wooriverygood.api.member.exception.MemberNotFoundException; +import org.wooriverygood.api.member.repository.MemberRepository; import org.wooriverygood.api.review.domain.Review; import org.wooriverygood.api.review.dto.ReviewUpdateRequest; import org.wooriverygood.api.review.exception.ReviewNotFoundException; @@ -15,19 +18,23 @@ public class ReviewUpdateService { private final ReviewRepository reviewRepository; + private final MemberRepository memberRepository; + @Transactional public void updateReview(Long reviewId, ReviewUpdateRequest request, AuthInfo authInfo) { Review review = reviewRepository.findById(reviewId) .orElseThrow(ReviewNotFoundException::new); - review.validateAuthor(authInfo.getUsername()); + Member member = memberRepository.findById(authInfo.getMemberId()) + .orElseThrow(MemberNotFoundException::new); + review.validateAuthor(member); review.updateTitle(request.getReviewTitle()); review.updateInstructorName(request.getInstructorName()); review.updateTakenSemyr(request.getTakenSemyr()); review.updateContent(request.getReviewContent()); review.updateGrade(request.getGrade()); - review.updateAuthor(authInfo.getUsername()); +// review.updateAuthor(authInfo.getUsername()); } } diff --git a/src/main/java/org/wooriverygood/api/review/application/ReviewValidateAccessService.java b/src/main/java/org/wooriverygood/api/review/application/ReviewValidateAccessService.java index d6b2002..625f790 100644 --- a/src/main/java/org/wooriverygood/api/review/application/ReviewValidateAccessService.java +++ b/src/main/java/org/wooriverygood/api/review/application/ReviewValidateAccessService.java @@ -3,6 +3,9 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.wooriverygood.api.member.domain.Member; +import org.wooriverygood.api.member.exception.MemberNotFoundException; +import org.wooriverygood.api.member.repository.MemberRepository; import org.wooriverygood.api.review.exception.ReviewAccessDeniedException; import org.wooriverygood.api.global.auth.AuthInfo; import org.wooriverygood.api.review.domain.Review; @@ -18,8 +21,13 @@ public class ReviewValidateAccessService { private final ReviewRepository reviewRepository; + private final MemberRepository memberRepository; + + public void validateReviewAccess(AuthInfo authInfo) { - Review review = reviewRepository.findTopByAuthorEmailOrderByCreatedAtDesc(authInfo.getUsername()) + Member member = memberRepository.findById(authInfo.getMemberId()) + .orElseThrow(MemberNotFoundException::new); + Review review = reviewRepository.findTopByMemberOrderByCreatedAtDesc(member) .orElseThrow(ReviewAccessDeniedException::new); LocalDateTime now = LocalDateTime.now(); diff --git a/src/main/java/org/wooriverygood/api/review/domain/Review.java b/src/main/java/org/wooriverygood/api/review/domain/Review.java index 787f5f8..07a875d 100644 --- a/src/main/java/org/wooriverygood/api/review/domain/Review.java +++ b/src/main/java/org/wooriverygood/api/review/domain/Review.java @@ -9,6 +9,7 @@ import org.springframework.data.jpa.domain.support.AuditingEntityListener; import org.wooriverygood.api.global.error.exception.AuthorizationException; import org.wooriverygood.api.course.domain.Course; +import org.wooriverygood.api.member.domain.Member; import java.time.LocalDateTime; import java.util.List; @@ -46,6 +47,9 @@ public class Review { @Column(name = "author_email", length = 300) private String authorEmail; + @ManyToOne(fetch = FetchType.LAZY) + private Member member; + @Column(name = "like_count") @ColumnDefault("0") private int likeCount; @@ -61,7 +65,9 @@ public class Review { private boolean updated; @Builder - public Review(Long id, Course course, String reviewContent, String reviewTitle, String instructorName, String takenSemyr, String grade, String authorEmail, LocalDateTime createdAt, List reviewLikes, boolean updated) { + public Review(Long id, Course course, String reviewContent, String reviewTitle, String instructorName, + String takenSemyr, String grade, String authorEmail, LocalDateTime createdAt, Member member, + List reviewLikes, boolean updated) { this.id = id; this.course = course; this.reviewContent = reviewContent; @@ -73,16 +79,15 @@ public Review(Long id, Course course, String reviewContent, String reviewTitle, this.createdAt = createdAt; this.reviewLikes = reviewLikes; this.updated = updated; + this.member = member; } - public boolean isSameAuthor(String author) { - return this.authorEmail.equals(author); + public boolean isSameAuthor(Member member) { + return this.member.isSame(member); } - public void validateAuthor(String author) { - if (!this.authorEmail.equals(author)) { - throw new AuthorizationException(); - } + public void validateAuthor(Member member) { + this.member.verify(member); } public void addReviewLike(ReviewLike reviewLike) { @@ -119,9 +124,9 @@ public void updateGrade(String grade) { updated = true; } - public void updateAuthor(String author) { - authorEmail = author; - updated = true; - } +// public void updateAuthor(String author) { +// authorEmail = author; +// updated = true; +// } } diff --git a/src/main/java/org/wooriverygood/api/review/repository/ReviewLikeRepository.java b/src/main/java/org/wooriverygood/api/review/repository/ReviewLikeRepository.java index da7077e..f9f5817 100644 --- a/src/main/java/org/wooriverygood/api/review/repository/ReviewLikeRepository.java +++ b/src/main/java/org/wooriverygood/api/review/repository/ReviewLikeRepository.java @@ -1,6 +1,7 @@ package org.wooriverygood.api.review.repository; import org.springframework.data.jpa.repository.JpaRepository; +import org.wooriverygood.api.member.domain.Member; import org.wooriverygood.api.review.domain.Review; import org.wooriverygood.api.review.domain.ReviewLike; @@ -8,7 +9,7 @@ import java.util.Optional; public interface ReviewLikeRepository extends JpaRepository { - Optional findByReviewAndUsername(Review review, String username); - boolean existsByReviewAndUsername(Review review, String username); + Optional findByReviewAndMember(Review review, Member member); + boolean existsByReviewAndMember(Review review, Member member); void deleteAllByReview(Review review); } diff --git a/src/main/java/org/wooriverygood/api/review/repository/ReviewRepository.java b/src/main/java/org/wooriverygood/api/review/repository/ReviewRepository.java index ef21a4e..b2957a1 100644 --- a/src/main/java/org/wooriverygood/api/review/repository/ReviewRepository.java +++ b/src/main/java/org/wooriverygood/api/review/repository/ReviewRepository.java @@ -4,6 +4,7 @@ import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.transaction.annotation.Transactional; +import org.wooriverygood.api.member.domain.Member; import org.wooriverygood.api.review.domain.Review; import java.util.List; @@ -13,9 +14,7 @@ public interface ReviewRepository extends JpaRepository { List findAllByCourseId(Long courseId); - List findByAuthorEmail(String author); - - Optional findTopByAuthorEmailOrderByCreatedAtDesc(String author); + Optional findTopByMemberOrderByCreatedAtDesc(Member member); void deleteAllInBatch(); diff --git a/src/test/java/org/wooriverygood/api/comment/application/CommentCreateServiceTest.java b/src/test/java/org/wooriverygood/api/comment/application/CommentCreateServiceTest.java index d622f25..e0af7a2 100644 --- a/src/test/java/org/wooriverygood/api/comment/application/CommentCreateServiceTest.java +++ b/src/test/java/org/wooriverygood/api/comment/application/CommentCreateServiceTest.java @@ -10,6 +10,7 @@ import org.wooriverygood.api.comment.dto.NewCommentRequest; import org.wooriverygood.api.comment.dto.NewReplyRequest; import org.wooriverygood.api.comment.repository.CommentRepository; +import org.wooriverygood.api.member.repository.MemberRepository; import org.wooriverygood.api.post.domain.Post; import org.wooriverygood.api.post.domain.PostCategory; import org.wooriverygood.api.post.repository.PostRepository; @@ -36,6 +37,9 @@ class CommentCreateServiceTest extends MockTest { @Mock private CommentRepository commentRepository; + @Mock + private MemberRepository memberRepository; + private Post post = Post.builder() .id(6L) .category(PostCategory.OFFER) @@ -93,7 +97,7 @@ void addReply() { .thenReturn(Optional.ofNullable(comment)); commentCreateService.addReply(1L, request, authInfo); - Comment reply = comment.getChildren().get(0); + Comment reply = comment.getReplies().get(0); assertThat(reply.getContent()).isEqualTo(request.getContent()); assertThat(reply.getParent()).isEqualTo(comment); diff --git a/src/test/java/org/wooriverygood/api/comment/application/CommentDeleteServiceTest.java b/src/test/java/org/wooriverygood/api/comment/application/CommentDeleteServiceTest.java index 1e02536..6ef297a 100644 --- a/src/test/java/org/wooriverygood/api/comment/application/CommentDeleteServiceTest.java +++ b/src/test/java/org/wooriverygood/api/comment/application/CommentDeleteServiceTest.java @@ -65,7 +65,7 @@ void deleteReply() { verify(reply).validateAuthor(authInfo.getUsername()); verify(commentLikeRepository).deleteAllByComment(reply); - verify(comment).deleteChild(reply); + verify(comment).deleteReply(reply); verify(commentRepository).delete(reply); } @@ -111,7 +111,7 @@ void deletePrentAndReply() { commentDeleteService.deleteComment(reply.getId(), authInfo); - verify(comment).deleteChild(reply); + verify(comment).deleteReply(reply); verify(commentRepository).delete(reply); verify(commentRepository).delete(comment); } diff --git a/src/test/java/org/wooriverygood/api/comment/application/CommentFindServiceTest.java b/src/test/java/org/wooriverygood/api/comment/application/CommentFindServiceTest.java index 762f447..dec18e7 100644 --- a/src/test/java/org/wooriverygood/api/comment/application/CommentFindServiceTest.java +++ b/src/test/java/org/wooriverygood/api/comment/application/CommentFindServiceTest.java @@ -134,7 +134,7 @@ void findAllCommentsByPostId_withReplies() { .parent(comment) .build(); comments.add(reply); - comment.addChildren(reply); + comment.addReply(reply); when(commentRepository.findAllByPostId(anyLong())) .thenReturn(comments); diff --git a/src/test/java/org/wooriverygood/api/comment/application/CommentLikeToggleServiceTest.java b/src/test/java/org/wooriverygood/api/comment/application/CommentLikeToggleServiceTest.java index d20e806..6c06998 100644 --- a/src/test/java/org/wooriverygood/api/comment/application/CommentLikeToggleServiceTest.java +++ b/src/test/java/org/wooriverygood/api/comment/application/CommentLikeToggleServiceTest.java @@ -67,7 +67,7 @@ class CommentLikeToggleServiceTest extends MockTest { @BeforeEach void setUpPosts() { - comment.getChildren().add(reply); + comment.getReplies().add(reply); for (int i = 0; i < COMMENT_COUNT; i++) { Comment comment = Comment.builder() diff --git a/src/test/java/org/wooriverygood/api/review/application/ReviewCreateServiceTest.java b/src/test/java/org/wooriverygood/api/review/application/ReviewCreateServiceTest.java index dda08e1..a5ac22d 100644 --- a/src/test/java/org/wooriverygood/api/review/application/ReviewCreateServiceTest.java +++ b/src/test/java/org/wooriverygood/api/review/application/ReviewCreateServiceTest.java @@ -7,6 +7,7 @@ import org.wooriverygood.api.course.domain.Course; import org.wooriverygood.api.course.repository.CourseRepository; import org.wooriverygood.api.global.auth.AuthInfo; +import org.wooriverygood.api.member.repository.MemberRepository; import org.wooriverygood.api.review.domain.Review; import org.wooriverygood.api.review.dto.NewReviewRequest; import org.wooriverygood.api.review.repository.ReviewRepository; @@ -14,6 +15,7 @@ import java.util.Optional; +import static org.junit.jupiter.api.Assertions.assertAll; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.*; @@ -29,10 +31,8 @@ class ReviewCreateServiceTest extends MockTest { @Mock private ReviewRepository reviewRepository; - private AuthInfo authInfo = AuthInfo.builder() - .sub("22222-34534-123") - .username("22222-34534-123") - .build(); + @Mock + private MemberRepository memberRepository; private Course course = Course.builder() .id(1L) @@ -57,11 +57,15 @@ void addReview() { when(courseRepository.findById(anyLong())) .thenReturn(Optional.ofNullable(course)); + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); reviewCreateService.addReview(authInfo, 1L, request); - verify(reviewRepository).save(any(Review.class)); - verify(courseRepository).increaseReviewCount(course.getId()); + assertAll( + () -> verify(reviewRepository).save(any(Review.class)), + () -> verify(courseRepository).increaseReviewCount(course.getId()) + ); } } \ No newline at end of file diff --git a/src/test/java/org/wooriverygood/api/review/application/ReviewDeleteServiceTest.java b/src/test/java/org/wooriverygood/api/review/application/ReviewDeleteServiceTest.java index bad8f86..fa83078 100644 --- a/src/test/java/org/wooriverygood/api/review/application/ReviewDeleteServiceTest.java +++ b/src/test/java/org/wooriverygood/api/review/application/ReviewDeleteServiceTest.java @@ -8,6 +8,8 @@ import org.wooriverygood.api.course.repository.CourseRepository; import org.wooriverygood.api.global.auth.AuthInfo; import org.wooriverygood.api.global.error.exception.AuthorizationException; +import org.wooriverygood.api.member.domain.Member; +import org.wooriverygood.api.member.repository.MemberRepository; import org.wooriverygood.api.review.domain.Review; import org.wooriverygood.api.review.exception.ReviewNotFoundException; import org.wooriverygood.api.review.repository.ReviewLikeRepository; @@ -17,6 +19,7 @@ import java.util.Optional; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.*; @@ -35,42 +38,46 @@ class ReviewDeleteServiceTest extends MockTest { @Mock private ReviewLikeRepository reviewLikeRepository; + @Mock + private MemberRepository memberRepository; + private Course course = Course.builder() .id(1L) .build(); - private AuthInfo authInfo = AuthInfo.builder() - .sub("22222-34534-123") - .username("22222-34534-123") + private Review review = Review.builder() + .member(member) + .course(course) .build(); - @Mock - private Review review; - @Test @DisplayName("권한이 있는 리뷰를 삭제한다.") void deleteReview() { when(reviewRepository.findById(anyLong())) .thenReturn(Optional.ofNullable(review)); - when(review.getCourse()) - .thenReturn(course); + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); reviewDeleteService.deleteReview(1L, authInfo); - verify(review).validateAuthor(authInfo.getUsername()); - verify(reviewLikeRepository).deleteAllByReview(review); - verify(reviewRepository).delete(review); - verify(courseRepository).decreaseReviewCount(course.getId()); + assertAll( + () -> verify(reviewLikeRepository).deleteAllByReview(review), + () -> verify(reviewRepository).delete(review), + () -> verify(courseRepository).decreaseReviewCount(course.getId()) + ); } @Test @DisplayName("권한이 없는 리뷰는 삭제가 불가능하다.") void deleteReview_exception_noAuth() { + Review review = Review.builder() + .id(2L) + .member(new Member(5L, "noAuth")) + .build(); when(reviewRepository.findById(anyLong())) .thenReturn(Optional.ofNullable(review)); - doThrow(new AuthorizationException()) - .when(review) - .validateAuthor(anyString()); + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); assertThatThrownBy(() -> reviewDeleteService.deleteReview(1L, authInfo)) .isInstanceOf(AuthorizationException.class); diff --git a/src/test/java/org/wooriverygood/api/review/application/ReviewFindServiceTest.java b/src/test/java/org/wooriverygood/api/review/application/ReviewFindServiceTest.java index 29677fb..24d5dea 100644 --- a/src/test/java/org/wooriverygood/api/review/application/ReviewFindServiceTest.java +++ b/src/test/java/org/wooriverygood/api/review/application/ReviewFindServiceTest.java @@ -6,7 +6,8 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.wooriverygood.api.course.domain.Course; -import org.wooriverygood.api.global.auth.AuthInfo; +import org.wooriverygood.api.member.domain.Member; +import org.wooriverygood.api.member.repository.MemberRepository; import org.wooriverygood.api.review.domain.Review; import org.wooriverygood.api.review.dto.ReviewsResponse; import org.wooriverygood.api.review.repository.ReviewLikeRepository; @@ -15,10 +16,10 @@ import java.util.ArrayList; import java.util.List; +import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.when; class ReviewFindServiceTest extends MockTest { @@ -32,12 +33,10 @@ class ReviewFindServiceTest extends MockTest { @Mock private ReviewLikeRepository reviewLikeRepository; - private List reviews = new ArrayList<>(); + @Mock + private MemberRepository memberRepository; - private AuthInfo authInfo = AuthInfo.builder() - .sub("22222-34534-123") - .username("22222-34534-123") - .build(); + private List reviews = new ArrayList<>(); private Course course = Course.builder() .id(1L) @@ -57,6 +56,7 @@ void setUp() { Review review = Review.builder() .id(i) .course(course) + .member(member) .reviewContent("review" + i) .reviewTitle("review" + i) .instructorName("jiaoshou") @@ -75,7 +75,9 @@ void setUp() { void findAllReviewsByCourseId() { when(reviewRepository.findAllByCourseId(any())) .thenReturn(reviews); - when(reviewLikeRepository.existsByReviewAndUsername(any(Review.class), anyString())) + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); + when(reviewLikeRepository.existsByReviewAndMember(any(Review.class), any(Member.class))) .thenReturn(true); ReviewsResponse response = reviewFindService.findAllReviewsByCourseId(1L, authInfo); @@ -87,8 +89,11 @@ void findAllReviewsByCourseId() { @Test @DisplayName("사용자 본인이 작성한 리뷰들을 불러온다.") void findMyReviews() { - when(reviewRepository.findByAuthorEmail(any(String.class))) - .thenReturn(reviews); + for (Review review : reviews) { + member.addReview(review); + } + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); ReviewsResponse response = reviewFindService.findMyReviews(authInfo); diff --git a/src/test/java/org/wooriverygood/api/review/application/ReviewLikeToggleServiceTest.java b/src/test/java/org/wooriverygood/api/review/application/ReviewLikeToggleServiceTest.java index 430a4ad..f4896cf 100644 --- a/src/test/java/org/wooriverygood/api/review/application/ReviewLikeToggleServiceTest.java +++ b/src/test/java/org/wooriverygood/api/review/application/ReviewLikeToggleServiceTest.java @@ -6,23 +6,26 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.wooriverygood.api.member.domain.Member; +import org.wooriverygood.api.member.repository.MemberRepository; import org.wooriverygood.api.review.domain.Review; import org.wooriverygood.api.review.domain.ReviewLike; import org.wooriverygood.api.review.dto.*; import org.wooriverygood.api.review.repository.ReviewLikeRepository; import org.wooriverygood.api.review.repository.ReviewRepository; import org.wooriverygood.api.global.auth.AuthInfo; +import org.wooriverygood.api.util.MockTest; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.when; -@ExtendWith(MockitoExtension.class) -public class ReviewLikeToggleServiceTest { +public class ReviewLikeToggleServiceTest extends MockTest { @InjectMocks private ReviewLikeToggleService reviewLikeToggleService; @@ -33,10 +36,8 @@ public class ReviewLikeToggleServiceTest { @Mock private ReviewLikeRepository reviewLikeRepository; - private final AuthInfo authInfo = AuthInfo.builder() - .sub("22222-34534-123") - .username("22222-34534-123") - .build(); + @Mock + private MemberRepository memberRepository; private final Review review = Review.builder() .id(1L) @@ -45,7 +46,7 @@ public class ReviewLikeToggleServiceTest { .instructorName("jiaoshou") .takenSemyr("22-23") .grade("100") - .authorEmail(authInfo.getUsername()) + .member(member) .reviewLikes(new ArrayList<>()) .updated(false) .createdAt(LocalDateTime.of(2022, 6, 13, 12, 00)) @@ -57,11 +58,15 @@ public class ReviewLikeToggleServiceTest { void likeReview_up() { when(reviewRepository.findById(anyLong())) .thenReturn(Optional.ofNullable(review)); + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); ReviewLikeResponse response = reviewLikeToggleService.toggleReviewLike(review.getId(), authInfo); - assertThat(response.getLikeCount()).isEqualTo(review.getLikeCount() + 1); - assertThat(response.isLiked()).isEqualTo(true); + assertAll( + () -> assertThat(response.getLikeCount()).isEqualTo(review.getLikeCount() + 1), + () -> assertThat(response.isLiked()).isEqualTo(true) + ); } @Test @@ -75,7 +80,9 @@ void likeReview_down() { when(reviewRepository.findById(anyLong())) .thenReturn(Optional.ofNullable(review)); - when(reviewLikeRepository.findByReviewAndUsername(any(Review.class), anyString())) + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); + when(reviewLikeRepository.findByReviewAndMember(any(Review.class), any(Member.class))) .thenReturn(Optional.ofNullable(reviewLike)); ReviewLikeResponse response = reviewLikeToggleService.toggleReviewLike(review.getId(), authInfo); diff --git a/src/test/java/org/wooriverygood/api/review/application/ReviewUpdateServiceTest.java b/src/test/java/org/wooriverygood/api/review/application/ReviewUpdateServiceTest.java index 86a4cbe..6fcb895 100644 --- a/src/test/java/org/wooriverygood/api/review/application/ReviewUpdateServiceTest.java +++ b/src/test/java/org/wooriverygood/api/review/application/ReviewUpdateServiceTest.java @@ -6,6 +6,8 @@ import org.mockito.Mock; import org.wooriverygood.api.global.auth.AuthInfo; import org.wooriverygood.api.global.error.exception.AuthorizationException; +import org.wooriverygood.api.member.domain.Member; +import org.wooriverygood.api.member.repository.MemberRepository; import org.wooriverygood.api.review.domain.Review; import org.wooriverygood.api.review.dto.ReviewUpdateRequest; import org.wooriverygood.api.review.repository.ReviewRepository; @@ -13,7 +15,9 @@ import java.util.Optional; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; @@ -25,13 +29,13 @@ class ReviewUpdateServiceTest extends MockTest { @Mock private ReviewRepository reviewRepository; - private AuthInfo authInfo = AuthInfo.builder() - .sub("22222-34534-123") - .username("22222-34534-123") - .build(); - @Mock - private Review review; + private MemberRepository memberRepository; + + private Review review = Review.builder() + .id(1L) + .member(member) + .build(); @Test @DisplayName("권한이 있는 리뷰를 수정한다.") @@ -44,18 +48,20 @@ void updateReview() { .grade("100") .build(); - when(reviewRepository.findById(any(Long.class))) + when(reviewRepository.findById(anyLong())) .thenReturn(Optional.ofNullable(review)); + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); reviewUpdateService.updateReview(1L, request, authInfo); - verify(review).validateAuthor(authInfo.getUsername()); - verify(review).updateTitle(request.getReviewTitle()); - verify(review).updateInstructorName(request.getInstructorName()); - verify(review).updateTakenSemyr(request.getTakenSemyr()); - verify(review).updateContent(request.getReviewContent()); - verify(review).updateGrade(request.getGrade()); - verify(review).updateAuthor(authInfo.getUsername()); + assertAll( + () -> assertThat(review.getReviewTitle()).isEqualTo(request.getReviewTitle()), + () -> assertThat(review.getInstructorName()).isEqualTo(request.getInstructorName()), + () -> assertThat(review.getTakenSemyr()).isEqualTo(request.getTakenSemyr()), + () -> assertThat(review.getReviewContent()).isEqualTo(request.getReviewContent()), + () -> assertThat(review.getGrade()).isEqualTo(request.getGrade()) + ); } @Test @@ -64,13 +70,12 @@ void updateReview_noAuth() { ReviewUpdateRequest request = ReviewUpdateRequest.builder() .build(); - when(reviewRepository.findById(any(Long.class))) + when(reviewRepository.findById(anyLong())) .thenReturn(Optional.ofNullable(review)); - doThrow(new AuthorizationException()) - .when(review) - .validateAuthor(anyString()); + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.of(new Member(5L, "noAuth"))); - assertThatThrownBy(() -> reviewUpdateService.updateReview(1L, request, authInfo)) + assertThatThrownBy(() -> reviewUpdateService.updateReview(review.getId(), request, authInfo)) .isInstanceOf(AuthorizationException.class); } diff --git a/src/test/java/org/wooriverygood/api/review/application/ReviewValidateAccessServiceTest.java b/src/test/java/org/wooriverygood/api/review/application/ReviewValidateAccessServiceTest.java index 99375b1..e389a88 100644 --- a/src/test/java/org/wooriverygood/api/review/application/ReviewValidateAccessServiceTest.java +++ b/src/test/java/org/wooriverygood/api/review/application/ReviewValidateAccessServiceTest.java @@ -4,6 +4,9 @@ import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.wooriverygood.api.course.domain.Course; +import org.wooriverygood.api.member.domain.Member; +import org.wooriverygood.api.member.repository.MemberRepository; import org.wooriverygood.api.review.exception.ReviewAccessDeniedException; import org.wooriverygood.api.global.auth.AuthInfo; import org.wooriverygood.api.review.domain.Review; @@ -11,10 +14,12 @@ import org.wooriverygood.api.util.MockTest; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.when; class ReviewValidateAccessServiceTest extends MockTest { @@ -25,10 +30,8 @@ class ReviewValidateAccessServiceTest extends MockTest { @Mock private ReviewRepository reviewRepository; - private AuthInfo authInfo = AuthInfo.builder() - .sub("22222-34534-123") - .username("22222-34534-123") - .build(); + @Mock + private MemberRepository memberRepository; @Test @@ -38,7 +41,9 @@ void canAccessReviews_true() { .createdAt(LocalDateTime.now().minusMonths(6)) .build(); - when(reviewRepository.findTopByAuthorEmailOrderByCreatedAtDesc(any(String.class))) + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); + when(reviewRepository.findTopByMemberOrderByCreatedAtDesc(any(Member.class))) .thenReturn(Optional.ofNullable(review)); reviewValidateAccessService.validateReviewAccess(authInfo); @@ -47,7 +52,9 @@ void canAccessReviews_true() { @Test @DisplayName("리뷰를 작성하지 않았다면, 예외를 발생시킨다.") void canAccessReviews_false_noReview() { - when(reviewRepository.findTopByAuthorEmailOrderByCreatedAtDesc(any(String.class))) + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); + when(reviewRepository.findTopByMemberOrderByCreatedAtDesc(any(Member.class))) .thenReturn(Optional.empty()); assertThatThrownBy(() -> reviewValidateAccessService.validateReviewAccess(authInfo)) @@ -61,7 +68,9 @@ void canAccessReviews_false_sixMonths() { .createdAt(LocalDateTime.of(2022, 6, 13, 12, 00)) .build(); - when(reviewRepository.findTopByAuthorEmailOrderByCreatedAtDesc(any(String.class))) + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); + when(reviewRepository.findTopByMemberOrderByCreatedAtDesc(any(Member.class))) .thenReturn(Optional.ofNullable(review)); assertThatThrownBy(() -> reviewValidateAccessService.validateReviewAccess(authInfo)) diff --git a/src/test/java/org/wooriverygood/api/util/MockTest.java b/src/test/java/org/wooriverygood/api/util/MockTest.java index e8ee391..36217d3 100644 --- a/src/test/java/org/wooriverygood/api/util/MockTest.java +++ b/src/test/java/org/wooriverygood/api/util/MockTest.java @@ -3,13 +3,17 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; import org.wooriverygood.api.global.auth.AuthInfo; +import org.wooriverygood.api.member.domain.Member; @ExtendWith(MockitoExtension.class) public class MockTest { protected AuthInfo authInfo = AuthInfo.builder() + .memberId(1L) .sub("22222-34534-123") .username("22222-34534-123") .build(); + protected Member member = new Member(1L, authInfo.getUsername()); + } From c5cb748728cf1fb6f75dd3532e65b7576c7cdec7 Mon Sep 17 00:00:00 2001 From: Park Geonho Date: Sun, 28 Apr 2024 22:55:04 +0800 Subject: [PATCH 17/21] =?UTF-8?q?[fix]=20Post=EC=99=80=20Report=20?= =?UTF-8?q?=ED=8C=A8=ED=82=A4=EC=A7=80=EC=97=90=20Member=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/comment/domain/Comment.java | 4 +- .../post/application/PostCreateService.java | 15 +++- .../post/application/PostDeleteService.java | 11 ++- .../api/post/application/PostFindService.java | 28 +++++-- .../application/PostLikeToggleService.java | 9 ++- .../post/application/PostUpdateService.java | 12 ++- .../wooriverygood/api/post/domain/Post.java | 19 +++-- .../api/post/dto/PostDetailResponse.java | 17 ++-- .../api/post/dto/PostResponse.java | 12 ++- .../post/repository/PostLikeRepository.java | 5 +- .../application/CommentReportService.java | 15 +++- .../report/application/PostReportService.java | 15 +++- .../api/report/domain/CommentReport.java | 14 ++-- .../api/report/domain/PostReport.java | 13 +-- .../application/ReviewUpdateService.java | 1 - .../api/review/domain/Review.java | 7 +- .../api/comment/api/CommentApiTest.java | 3 +- .../application/CommentCreateServiceTest.java | 12 +-- .../application/CommentFindServiceTest.java | 12 +-- .../CommentLikeToggleServiceTest.java | 12 +-- .../application/CommentServiceTest.java | 27 +++++++ .../application/CommentUpdateServiceTest.java | 12 +-- .../application/PostCreateServiceTest.java | 23 +++--- .../application/PostDeleteServiceTest.java | 69 ++++++---------- .../post/application/PostFindServiceTest.java | 80 ++++++++++--------- .../PostLikeToggleServiceTest.java | 48 ++++++----- .../api/post/application/PostServiceTest.java | 27 +++++++ .../application/PostUpdateServiceTest.java | 46 +++++------ .../application/CommentReportServiceTest.java | 70 ++++++++++------ .../application/PostReportServiceTest.java | 15 ++-- .../org/wooriverygood/api/util/MockTest.java | 9 ++- 31 files changed, 379 insertions(+), 283 deletions(-) create mode 100644 src/test/java/org/wooriverygood/api/comment/application/CommentServiceTest.java create mode 100644 src/test/java/org/wooriverygood/api/post/application/PostServiceTest.java diff --git a/src/main/java/org/wooriverygood/api/comment/domain/Comment.java b/src/main/java/org/wooriverygood/api/comment/domain/Comment.java index f87c8c8..70b9b9e 100644 --- a/src/main/java/org/wooriverygood/api/comment/domain/Comment.java +++ b/src/main/java/org/wooriverygood/api/comment/domain/Comment.java @@ -101,9 +101,9 @@ public void addReport(CommentReport report) { reports.add(report); } - public boolean hasReportByUser(String username) { + public boolean hasReportByMember(Member member) { for (CommentReport report: reports) - if (report.isOwner(username)) + if (report.isOwner(member)) return true; return false; } diff --git a/src/main/java/org/wooriverygood/api/post/application/PostCreateService.java b/src/main/java/org/wooriverygood/api/post/application/PostCreateService.java index 50dfeff..403b65a 100644 --- a/src/main/java/org/wooriverygood/api/post/application/PostCreateService.java +++ b/src/main/java/org/wooriverygood/api/post/application/PostCreateService.java @@ -3,6 +3,9 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.wooriverygood.api.member.domain.Member; +import org.wooriverygood.api.member.exception.MemberNotFoundException; +import org.wooriverygood.api.member.repository.MemberRepository; import org.wooriverygood.api.post.domain.Post; import org.wooriverygood.api.post.domain.PostCategory; import org.wooriverygood.api.post.dto.NewPostRequest; @@ -10,24 +13,28 @@ import org.wooriverygood.api.global.auth.AuthInfo; @Service +@Transactional @RequiredArgsConstructor public class PostCreateService { private final PostRepository postRepository; - @Transactional + private final MemberRepository memberRepository; + public void addPost(AuthInfo authInfo, NewPostRequest newPostRequest) { + Member member = memberRepository.findById(authInfo.getMemberId()) + .orElseThrow(MemberNotFoundException::new); PostCategory.parse(newPostRequest.getPostCategory()); - Post post = createPost(authInfo, newPostRequest); + Post post = createPost(member, newPostRequest); postRepository.save(post); } - private Post createPost(AuthInfo authInfo, NewPostRequest newPostRequest) { + private Post createPost(Member member, NewPostRequest newPostRequest) { return Post.builder() .title(newPostRequest.getPostTitle()) .content(newPostRequest.getPostContent()) .category(PostCategory.parse(newPostRequest.getPostCategory())) - .author(authInfo.getUsername()) + .member(member) .build(); } diff --git a/src/main/java/org/wooriverygood/api/post/application/PostDeleteService.java b/src/main/java/org/wooriverygood/api/post/application/PostDeleteService.java index aec5f1c..feeed2c 100644 --- a/src/main/java/org/wooriverygood/api/post/application/PostDeleteService.java +++ b/src/main/java/org/wooriverygood/api/post/application/PostDeleteService.java @@ -4,6 +4,9 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.wooriverygood.api.comment.repository.CommentRepository; +import org.wooriverygood.api.member.domain.Member; +import org.wooriverygood.api.member.exception.MemberNotFoundException; +import org.wooriverygood.api.member.repository.MemberRepository; import org.wooriverygood.api.post.domain.Post; import org.wooriverygood.api.post.exception.PostNotFoundException; import org.wooriverygood.api.post.repository.PostLikeRepository; @@ -11,6 +14,7 @@ import org.wooriverygood.api.global.auth.AuthInfo; @Service +@Transactional @RequiredArgsConstructor public class PostDeleteService { @@ -20,12 +24,15 @@ public class PostDeleteService { private final PostLikeRepository postLikeRepository; + private final MemberRepository memberRepository; + - @Transactional public void deletePost(AuthInfo authInfo, long postId) { + Member member = memberRepository.findById(authInfo.getMemberId()) + .orElseThrow(MemberNotFoundException::new); Post post = postRepository.findById(postId) .orElseThrow(PostNotFoundException::new); - post.validateAuthor(authInfo.getUsername()); + post.validateAuthor(member); deleteCommentAndPostLike(post); postRepository.delete(post); diff --git a/src/main/java/org/wooriverygood/api/post/application/PostFindService.java b/src/main/java/org/wooriverygood/api/post/application/PostFindService.java index b60439a..f2ca9f7 100644 --- a/src/main/java/org/wooriverygood/api/post/application/PostFindService.java +++ b/src/main/java/org/wooriverygood/api/post/application/PostFindService.java @@ -5,6 +5,9 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.wooriverygood.api.member.domain.Member; +import org.wooriverygood.api.member.exception.MemberNotFoundException; +import org.wooriverygood.api.member.repository.MemberRepository; import org.wooriverygood.api.post.domain.Post; import org.wooriverygood.api.post.domain.PostCategory; import org.wooriverygood.api.post.dto.PostDetailResponse; @@ -18,18 +21,22 @@ import java.util.List; @Service -@Transactional(readOnly = true) @RequiredArgsConstructor +@Transactional(readOnly = true) public class PostFindService { private final PostRepository postRepository; private final PostLikeRepository postLikeRepository; + private final MemberRepository memberRepository; + public PostsResponse findPosts(AuthInfo authInfo, Pageable pageable, String postCategory) { Page page = findPostsPage(pageable, postCategory); - return convertToPostsResponse(authInfo, page); + Member member = memberRepository.findById(authInfo.getMemberId()) + .orElseThrow(MemberNotFoundException::new); + return convertToPostsResponse(member, page); } private Page findPostsPage(Pageable pageable, String postCategory) { @@ -43,21 +50,26 @@ private Page findPostsPage(Pageable pageable, String postCategory) { public PostDetailResponse findPostById(long postId, AuthInfo authInfo) { Post post = postRepository.findById(postId) .orElseThrow(PostNotFoundException::new); - boolean liked = postLikeRepository.existsByPostAndUsername(post, authInfo.getUsername()); + Member member = memberRepository.findById(authInfo.getMemberId()) + .orElseThrow(MemberNotFoundException::new); + boolean liked = postLikeRepository.existsByPostAndMember(post, member); + boolean isMine = post.getMember().equals(member); postRepository.increaseViewCount(postId); - return PostDetailResponse.of(post, liked); + return PostDetailResponse.of(post, member.getId(), liked, isMine); } public PostsResponse findMyPosts(AuthInfo authInfo, Pageable pageable) { Page page = postRepository.findByAuthorOrderByIdDesc(authInfo.getUsername(), pageable); - return convertToPostsResponse(authInfo, page); + Member member = memberRepository.findById(authInfo.getMemberId()) + .orElseThrow(MemberNotFoundException::new); + return convertToPostsResponse(member, page); } - private PostsResponse convertToPostsResponse(AuthInfo authInfo, Page page) { + private PostsResponse convertToPostsResponse(Member member, Page page) { List posts = page.getContent().stream() .map(post -> { - boolean liked = postLikeRepository.existsByPostAndUsername(post, authInfo.getUsername()); - return PostResponse.of(post, liked); + boolean liked = postLikeRepository.existsByPostAndMember(post, member); + return PostResponse.of(post, liked, post.getMember().equals(member)); }) .toList(); diff --git a/src/main/java/org/wooriverygood/api/post/application/PostLikeToggleService.java b/src/main/java/org/wooriverygood/api/post/application/PostLikeToggleService.java index e1536a0..01e61ee 100644 --- a/src/main/java/org/wooriverygood/api/post/application/PostLikeToggleService.java +++ b/src/main/java/org/wooriverygood/api/post/application/PostLikeToggleService.java @@ -3,6 +3,9 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.wooriverygood.api.member.domain.Member; +import org.wooriverygood.api.member.exception.MemberNotFoundException; +import org.wooriverygood.api.member.repository.MemberRepository; import org.wooriverygood.api.post.domain.Post; import org.wooriverygood.api.post.domain.PostLike; import org.wooriverygood.api.post.dto.PostLikeResponse; @@ -22,12 +25,16 @@ public class PostLikeToggleService { private final PostLikeRepository postLikeRepository; + private final MemberRepository memberRepository; + public PostLikeResponse togglePostLike(long postId, AuthInfo authInfo) { + Member member = memberRepository.findById(authInfo.getMemberId()) + .orElseThrow(MemberNotFoundException::new); Post post = postRepository.findById(postId) .orElseThrow(PostNotFoundException::new); - Optional postLike = postLikeRepository.findByPostAndUsername(post, authInfo.getUsername()); + Optional postLike = postLikeRepository.findByPostAndMember(post, member); if (postLike.isEmpty()) { addPostLike(post, authInfo.getUsername()); diff --git a/src/main/java/org/wooriverygood/api/post/application/PostUpdateService.java b/src/main/java/org/wooriverygood/api/post/application/PostUpdateService.java index a2b8b17..0978f36 100644 --- a/src/main/java/org/wooriverygood/api/post/application/PostUpdateService.java +++ b/src/main/java/org/wooriverygood/api/post/application/PostUpdateService.java @@ -3,6 +3,9 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.wooriverygood.api.member.domain.Member; +import org.wooriverygood.api.member.exception.MemberNotFoundException; +import org.wooriverygood.api.member.repository.MemberRepository; import org.wooriverygood.api.post.domain.Post; import org.wooriverygood.api.post.dto.PostUpdateRequest; import org.wooriverygood.api.post.exception.PostNotFoundException; @@ -10,16 +13,21 @@ import org.wooriverygood.api.global.auth.AuthInfo; @Service +@Transactional @RequiredArgsConstructor public class PostUpdateService { private final PostRepository postRepository; - @Transactional + private final MemberRepository memberRepository; + + public void updatePost(long postId, PostUpdateRequest postUpdateRequest, AuthInfo authInfo) { + Member member = memberRepository.findById(authInfo.getMemberId()) + .orElseThrow(MemberNotFoundException::new); Post post = postRepository.findById(postId) .orElseThrow(PostNotFoundException::new); - post.validateAuthor(authInfo.getUsername()); + post.validateAuthor(member); post.updateTitle(postUpdateRequest.getPostTitle()); post.updateContent(postUpdateRequest.getPostContent()); diff --git a/src/main/java/org/wooriverygood/api/post/domain/Post.java b/src/main/java/org/wooriverygood/api/post/domain/Post.java index e9a4f06..a043f3c 100644 --- a/src/main/java/org/wooriverygood/api/post/domain/Post.java +++ b/src/main/java/org/wooriverygood/api/post/domain/Post.java @@ -7,6 +7,7 @@ import org.springframework.data.jpa.domain.support.AuditingEntityListener; import org.wooriverygood.api.global.error.exception.AuthorizationException; import org.wooriverygood.api.comment.domain.Comment; +import org.wooriverygood.api.member.domain.Member; import org.wooriverygood.api.report.domain.PostReport; import java.time.LocalDateTime; @@ -35,8 +36,8 @@ public class Post { @Embedded private Content content; - @Column(name = "post_author", length = 1000) - private String author; + @ManyToOne(fetch = FetchType.LAZY) + private Member member; @OneToMany(mappedBy = "post", fetch = FetchType.EAGER) private List comments = new ArrayList<>(); @@ -68,20 +69,22 @@ public class Post { @Builder - public Post(Long id, PostCategory category, String title, String content, String author, List comments, List postLikes, List reports, boolean updated) { + public Post(Long id, PostCategory category, String title, String content, + Member member, List comments, List postLikes, + List reports, boolean updated) { this.id = id; this.category = category; this.title = new Title(title); this.content = new Content(content); - this.author = author; + this.member = member; this.comments = comments; this.postLikes = postLikes; this.reports = reports; this.updated = updated; } - public void validateAuthor(String author) { - if (!this.author.equals(author)) throw new AuthorizationException(); + public void validateAuthor(Member member) { + this.member.verify(member); } public void addPostLike(PostLike postLike) { @@ -113,9 +116,9 @@ public int getCommentCount() { return comments.size(); } - public boolean hasReportByUser(String username) { + public boolean hasReportByMember(Member member) { for (PostReport report: reports) - if (report.isOwner(username)) + if (report.isOwner(member)) return true; return false; } diff --git a/src/main/java/org/wooriverygood/api/post/dto/PostDetailResponse.java b/src/main/java/org/wooriverygood/api/post/dto/PostDetailResponse.java index 0f28679..d6c4af1 100644 --- a/src/main/java/org/wooriverygood/api/post/dto/PostDetailResponse.java +++ b/src/main/java/org/wooriverygood/api/post/dto/PostDetailResponse.java @@ -20,7 +20,9 @@ public class PostDetailResponse { private final String postCategory; - private final String postAuthor; + private final long memberId; + + private final boolean isMine; private final int postComments; @@ -38,12 +40,16 @@ public class PostDetailResponse { @Builder - public PostDetailResponse(Long postId, String postTitle, String postContent, String postCategory, String postAuthor, int postComments, int postLikes, LocalDateTime postTime, boolean liked, boolean updated, boolean reported, int viewCount) { + public PostDetailResponse(Long postId, String postTitle, String postContent, + String postCategory, long memberId, int postComments, + int postLikes, LocalDateTime postTime, boolean liked, + boolean isMine, boolean updated, boolean reported, int viewCount) { this.postId = postId; this.postTitle = postTitle; this.postContent = postContent; this.postCategory = postCategory; - this.postAuthor = postAuthor; + this.memberId = memberId; + this.isMine = isMine; this.postComments = postComments; this.postLikes = postLikes; this.postTime = postTime; @@ -53,13 +59,14 @@ public PostDetailResponse(Long postId, String postTitle, String postContent, Str this.viewCount = viewCount; } - public static PostDetailResponse of(Post post, boolean liked) { + public static PostDetailResponse of(Post post, long memberId, boolean liked, boolean isMine) { return PostDetailResponse.builder() .postId(post.getId()) .postTitle(post.getTitle()) .postContent(post.getContent()) .postCategory(post.getCategory().getValue()) - .postAuthor(post.getAuthor()) + .isMine(isMine) + .memberId(memberId) .postComments(post.getCommentCount()) .postLikes(post.getLikeCount()) .postTime(post.getCreatedAt()) diff --git a/src/main/java/org/wooriverygood/api/post/dto/PostResponse.java b/src/main/java/org/wooriverygood/api/post/dto/PostResponse.java index a05a03f..7b4c21d 100644 --- a/src/main/java/org/wooriverygood/api/post/dto/PostResponse.java +++ b/src/main/java/org/wooriverygood/api/post/dto/PostResponse.java @@ -32,9 +32,13 @@ public class PostResponse { private final int viewCount; + private final boolean isMine; + + private final long memberId; + @Builder - public PostResponse(Long postId, String postTitle, String postCategory, int postComments, int postLikes, LocalDateTime postTime, boolean liked, boolean updated, boolean reported, int viewCount) { + public PostResponse(Long postId, String postTitle, String postCategory, int postComments, int postLikes, LocalDateTime postTime, boolean liked, boolean updated, boolean reported, int viewCount, boolean isMine, long memberId) { this.postId = postId; this.postTitle = postTitle; this.postCategory = postCategory; @@ -45,9 +49,11 @@ public PostResponse(Long postId, String postTitle, String postCategory, int post this.updated = updated; this.reported = reported; this.viewCount = viewCount; + this.isMine = isMine; + this.memberId = memberId; } - public static PostResponse of(Post post, boolean liked) { + public static PostResponse of(Post post, boolean liked, boolean isMine) { return PostResponse.builder() .postId(post.getId()) .postTitle(post.getTitle()) @@ -59,6 +65,8 @@ public static PostResponse of(Post post, boolean liked) { .updated(post.isUpdated()) .reported(post.isReportedTooMuch()) .viewCount(post.getViewCount()) + .memberId(post.getMember().getId()) + .isMine(isMine) .build(); } diff --git a/src/main/java/org/wooriverygood/api/post/repository/PostLikeRepository.java b/src/main/java/org/wooriverygood/api/post/repository/PostLikeRepository.java index b8f9a0a..f1ff129 100644 --- a/src/main/java/org/wooriverygood/api/post/repository/PostLikeRepository.java +++ b/src/main/java/org/wooriverygood/api/post/repository/PostLikeRepository.java @@ -1,6 +1,7 @@ package org.wooriverygood.api.post.repository; import org.springframework.data.jpa.repository.JpaRepository; +import org.wooriverygood.api.member.domain.Member; import org.wooriverygood.api.post.domain.Post; import org.wooriverygood.api.post.domain.PostLike; @@ -8,9 +9,9 @@ public interface PostLikeRepository extends JpaRepository { - Optional findByPostAndUsername(Post post, String username); + Optional findByPostAndMember(Post post, Member member); - boolean existsByPostAndUsername(Post post, String username); + boolean existsByPostAndMember(Post post, Member member); void deleteAllByPost(Post post); } diff --git a/src/main/java/org/wooriverygood/api/report/application/CommentReportService.java b/src/main/java/org/wooriverygood/api/report/application/CommentReportService.java index 9b2f2bf..0984bcb 100644 --- a/src/main/java/org/wooriverygood/api/report/application/CommentReportService.java +++ b/src/main/java/org/wooriverygood/api/report/application/CommentReportService.java @@ -4,6 +4,9 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.wooriverygood.api.comment.exception.CommentNotFoundException; +import org.wooriverygood.api.member.domain.Member; +import org.wooriverygood.api.member.exception.MemberNotFoundException; +import org.wooriverygood.api.member.repository.MemberRepository; import org.wooriverygood.api.report.exception.DuplicatedCommentReportException; import org.wooriverygood.api.comment.domain.Comment; import org.wooriverygood.api.comment.repository.CommentRepository; @@ -21,26 +24,30 @@ public class CommentReportService { private final CommentReportRepository commentReportRepository; + private final MemberRepository memberRepository; + public void reportComment(Long commentId, ReportRequest request, AuthInfo authInfo) { + Member member = memberRepository.findById(authInfo.getMemberId()) + .orElseThrow(MemberNotFoundException::new); Comment comment = commentRepository.findById(commentId) .orElseThrow(CommentNotFoundException::new); CommentReport report = CommentReport.builder() .comment(comment) .message(request.getMessage()) - .username(authInfo.getUsername()) + .member(member) .build(); - checkIfAlreadyReport(comment, authInfo); + checkIfAlreadyReport(comment, member); comment.addReport(report); commentRepository.increaseReportCount(commentId); commentReportRepository.save(report); } - private void checkIfAlreadyReport(Comment comment, AuthInfo authInfo) { - if (comment.hasReportByUser(authInfo.getUsername())) + private void checkIfAlreadyReport(Comment comment, Member member) { + if (comment.hasReportByMember(member)) throw new DuplicatedCommentReportException(); } } diff --git a/src/main/java/org/wooriverygood/api/report/application/PostReportService.java b/src/main/java/org/wooriverygood/api/report/application/PostReportService.java index 082672a..20d64bf 100644 --- a/src/main/java/org/wooriverygood/api/report/application/PostReportService.java +++ b/src/main/java/org/wooriverygood/api/report/application/PostReportService.java @@ -3,6 +3,9 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.wooriverygood.api.member.domain.Member; +import org.wooriverygood.api.member.exception.MemberNotFoundException; +import org.wooriverygood.api.member.repository.MemberRepository; import org.wooriverygood.api.report.exception.DuplicatedPostReportException; import org.wooriverygood.api.global.auth.AuthInfo; import org.wooriverygood.api.post.domain.Post; @@ -21,26 +24,30 @@ public class PostReportService { private final PostReportRepository postReportRepository; + private final MemberRepository memberRepository; + public void reportPost(Long postId, ReportRequest request, AuthInfo authInfo) { + Member member = memberRepository.findById(authInfo.getMemberId()) + .orElseThrow(MemberNotFoundException::new); Post post = postRepository.findById(postId) .orElseThrow(PostNotFoundException::new); PostReport report = PostReport.builder() .post(post) .message(request.getMessage()) - .username(authInfo.getUsername()) + .member(member) .build(); - checkIfAlreadyReport(post, authInfo); + checkIfAlreadyReport(post, member); post.addReport(report); postRepository.increaseReportCount(postId); postReportRepository.save(report); } - private void checkIfAlreadyReport(Post post, AuthInfo authInfo) { - if (post.hasReportByUser(authInfo.getUsername())) + private void checkIfAlreadyReport(Post post, Member member) { + if (post.hasReportByMember(member)) throw new DuplicatedPostReportException(); } diff --git a/src/main/java/org/wooriverygood/api/report/domain/CommentReport.java b/src/main/java/org/wooriverygood/api/report/domain/CommentReport.java index 336b0a6..483362c 100644 --- a/src/main/java/org/wooriverygood/api/report/domain/CommentReport.java +++ b/src/main/java/org/wooriverygood/api/report/domain/CommentReport.java @@ -5,9 +5,11 @@ import lombok.Getter; import lombok.NoArgsConstructor; import org.wooriverygood.api.comment.domain.Comment; +import org.wooriverygood.api.member.domain.Member; @Entity @Getter +@Table(name = "comment_reports") @NoArgsConstructor public class CommentReport { @@ -19,21 +21,21 @@ public class CommentReport { @JoinColumn(name = "comment_id", referencedColumnName = "comment_id") private Comment comment; - @Column - private String username; + @ManyToOne(fetch = FetchType.LAZY) + private Member member; private String message; @Builder - public CommentReport(Long id, Comment comment, String username, String message) { + public CommentReport(Long id, Comment comment, Member member, String message) { this.id = id; this.comment = comment; - this.username = username; + this.member = member; this.message = message; } - public boolean isOwner(String username) { - return this.username.equals(username); + public boolean isOwner(Member member) { + return this.member.equals(member); } } diff --git a/src/main/java/org/wooriverygood/api/report/domain/PostReport.java b/src/main/java/org/wooriverygood/api/report/domain/PostReport.java index 352684a..9c250cb 100644 --- a/src/main/java/org/wooriverygood/api/report/domain/PostReport.java +++ b/src/main/java/org/wooriverygood/api/report/domain/PostReport.java @@ -5,6 +5,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.wooriverygood.api.member.domain.Member; import org.wooriverygood.api.post.domain.Post; @Entity @@ -20,22 +21,22 @@ public class PostReport { @JoinColumn(name = "post_id", referencedColumnName = "post_id") private Post post; - @Column - private String username; + @ManyToOne(fetch = FetchType.LAZY) + private Member member; private String message; @Builder - public PostReport(Long id, Post post, String username, String message) { + public PostReport(Long id, Post post, Member member, String message) { this.id = id; this.post = post; - this.username = username; + this.member = member; this.message = message; } - public boolean isOwner(String username) { - return this.username.equals(username); + public boolean isOwner(Member member) { + return this.member.equals(member); } } diff --git a/src/main/java/org/wooriverygood/api/review/application/ReviewUpdateService.java b/src/main/java/org/wooriverygood/api/review/application/ReviewUpdateService.java index 9472407..d838810 100644 --- a/src/main/java/org/wooriverygood/api/review/application/ReviewUpdateService.java +++ b/src/main/java/org/wooriverygood/api/review/application/ReviewUpdateService.java @@ -34,7 +34,6 @@ public void updateReview(Long reviewId, ReviewUpdateRequest request, AuthInfo au review.updateTakenSemyr(request.getTakenSemyr()); review.updateContent(request.getReviewContent()); review.updateGrade(request.getGrade()); -// review.updateAuthor(authInfo.getUsername()); } } diff --git a/src/main/java/org/wooriverygood/api/review/domain/Review.java b/src/main/java/org/wooriverygood/api/review/domain/Review.java index 07a875d..3862efb 100644 --- a/src/main/java/org/wooriverygood/api/review/domain/Review.java +++ b/src/main/java/org/wooriverygood/api/review/domain/Review.java @@ -17,8 +17,8 @@ @Entity @Getter @NoArgsConstructor -@EntityListeners(AuditingEntityListener.class) @Table(name = "reviews") +@EntityListeners(AuditingEntityListener.class) public class Review { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -124,9 +124,4 @@ public void updateGrade(String grade) { updated = true; } -// public void updateAuthor(String author) { -// authorEmail = author; -// updated = true; -// } - } diff --git a/src/test/java/org/wooriverygood/api/comment/api/CommentApiTest.java b/src/test/java/org/wooriverygood/api/comment/api/CommentApiTest.java index afe7b04..9a8179d 100644 --- a/src/test/java/org/wooriverygood/api/comment/api/CommentApiTest.java +++ b/src/test/java/org/wooriverygood/api/comment/api/CommentApiTest.java @@ -8,6 +8,7 @@ import org.wooriverygood.api.global.error.exception.AuthorizationException; import org.wooriverygood.api.comment.exception.ReplyDepthException; import org.wooriverygood.api.comment.dto.*; +import org.wooriverygood.api.member.domain.Member; import org.wooriverygood.api.post.domain.Post; import org.wooriverygood.api.post.domain.PostCategory; import org.wooriverygood.api.global.auth.AuthInfo; @@ -31,7 +32,7 @@ class CommentApiTest extends ApiTest { .category(PostCategory.OFFER) .title("title6") .content("content6") - .author("user-3333") + .member(new Member(1L, "username")) .comments(new ArrayList<>()) .postLikes(new ArrayList<>()) .build(); diff --git a/src/test/java/org/wooriverygood/api/comment/application/CommentCreateServiceTest.java b/src/test/java/org/wooriverygood/api/comment/application/CommentCreateServiceTest.java index e0af7a2..9aa3fcc 100644 --- a/src/test/java/org/wooriverygood/api/comment/application/CommentCreateServiceTest.java +++ b/src/test/java/org/wooriverygood/api/comment/application/CommentCreateServiceTest.java @@ -26,7 +26,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -class CommentCreateServiceTest extends MockTest { +class CommentCreateServiceTest extends CommentServiceTest { @InjectMocks private CommentCreateService commentCreateService; @@ -40,16 +40,6 @@ class CommentCreateServiceTest extends MockTest { @Mock private MemberRepository memberRepository; - private Post post = Post.builder() - .id(6L) - .category(PostCategory.OFFER) - .title("title6") - .content("content6") - .author("author6") - .comments(new ArrayList<>()) - .postLikes(new ArrayList<>()) - .build(); - private Comment comment; private Comment reply; diff --git a/src/test/java/org/wooriverygood/api/comment/application/CommentFindServiceTest.java b/src/test/java/org/wooriverygood/api/comment/application/CommentFindServiceTest.java index dec18e7..6db7fe5 100644 --- a/src/test/java/org/wooriverygood/api/comment/application/CommentFindServiceTest.java +++ b/src/test/java/org/wooriverygood/api/comment/application/CommentFindServiceTest.java @@ -20,7 +20,7 @@ import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.when; -class CommentFindServiceTest extends MockTest { +class CommentFindServiceTest extends CommentServiceTest { @InjectMocks private CommentFindService commentFindService; @@ -35,16 +35,6 @@ class CommentFindServiceTest extends MockTest { private List comments = new ArrayList<>(); - private Post post = Post.builder() - .id(6L) - .category(PostCategory.OFFER) - .title("title6") - .content("content6") - .author("author6") - .comments(new ArrayList<>()) - .postLikes(new ArrayList<>()) - .build(); - @BeforeEach void setUp() { for (int i = 0; i < COMMENT_COUNT; i++) { diff --git a/src/test/java/org/wooriverygood/api/comment/application/CommentLikeToggleServiceTest.java b/src/test/java/org/wooriverygood/api/comment/application/CommentLikeToggleServiceTest.java index 6c06998..6d6c1be 100644 --- a/src/test/java/org/wooriverygood/api/comment/application/CommentLikeToggleServiceTest.java +++ b/src/test/java/org/wooriverygood/api/comment/application/CommentLikeToggleServiceTest.java @@ -22,7 +22,7 @@ import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.when; -class CommentLikeToggleServiceTest extends MockTest { +class CommentLikeToggleServiceTest extends CommentServiceTest { @InjectMocks private CommentLikeToggleService commentLikeToggleService; @@ -37,16 +37,6 @@ class CommentLikeToggleServiceTest extends MockTest { List comments = new ArrayList<>(); - private Post post = Post.builder() - .id(6L) - .category(PostCategory.OFFER) - .title("title6") - .content("content6") - .author("author6") - .comments(new ArrayList<>()) - .postLikes(new ArrayList<>()) - .build(); - private Comment comment = Comment.builder() .id(2L) .post(post) diff --git a/src/test/java/org/wooriverygood/api/comment/application/CommentServiceTest.java b/src/test/java/org/wooriverygood/api/comment/application/CommentServiceTest.java new file mode 100644 index 0000000..7a6e38b --- /dev/null +++ b/src/test/java/org/wooriverygood/api/comment/application/CommentServiceTest.java @@ -0,0 +1,27 @@ +package org.wooriverygood.api.comment.application; + +import org.junit.jupiter.api.BeforeEach; +import org.wooriverygood.api.post.domain.Post; +import org.wooriverygood.api.post.domain.PostCategory; +import org.wooriverygood.api.util.MockTest; + +import java.util.ArrayList; + +public class CommentServiceTest extends MockTest { + + protected Post post; + + @BeforeEach + void dateSetUp() { + post = Post.builder() + .id(1L) + .category(PostCategory.OFFER) + .title("simple title") + .content("simple content") + .member(member) + .comments(new ArrayList<>()) + .postLikes(new ArrayList<>()) + .build(); + } + +} diff --git a/src/test/java/org/wooriverygood/api/comment/application/CommentUpdateServiceTest.java b/src/test/java/org/wooriverygood/api/comment/application/CommentUpdateServiceTest.java index 5121407..da68f53 100644 --- a/src/test/java/org/wooriverygood/api/comment/application/CommentUpdateServiceTest.java +++ b/src/test/java/org/wooriverygood/api/comment/application/CommentUpdateServiceTest.java @@ -22,7 +22,7 @@ import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.when; -class CommentUpdateServiceTest extends MockTest { +class CommentUpdateServiceTest extends CommentServiceTest { @InjectMocks private CommentUpdateService commentUpdateService; @@ -30,16 +30,6 @@ class CommentUpdateServiceTest extends MockTest { @Mock private CommentRepository commentRepository; - private Post post = Post.builder() - .id(6L) - .category(PostCategory.OFFER) - .title("title6") - .content("content6") - .author("author6") - .comments(new ArrayList<>()) - .postLikes(new ArrayList<>()) - .build(); - private Comment comment; diff --git a/src/test/java/org/wooriverygood/api/post/application/PostCreateServiceTest.java b/src/test/java/org/wooriverygood/api/post/application/PostCreateServiceTest.java index 42b4581..ca0e67e 100644 --- a/src/test/java/org/wooriverygood/api/post/application/PostCreateServiceTest.java +++ b/src/test/java/org/wooriverygood/api/post/application/PostCreateServiceTest.java @@ -1,20 +1,24 @@ package org.wooriverygood.api.post.application; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.wooriverygood.api.member.domain.Member; +import org.wooriverygood.api.member.repository.MemberRepository; import org.wooriverygood.api.post.domain.Post; import org.wooriverygood.api.post.dto.NewPostRequest; import org.wooriverygood.api.post.exception.InvalidPostCategoryException; import org.wooriverygood.api.post.repository.PostRepository; -import org.wooriverygood.api.global.auth.AuthInfo; import org.wooriverygood.api.util.MockTest; +import java.util.Optional; + import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; class PostCreateServiceTest extends MockTest { @@ -24,15 +28,9 @@ class PostCreateServiceTest extends MockTest { @Mock private PostRepository postRepository; - private AuthInfo authInfo; + @Mock + private MemberRepository memberRepository; - @BeforeEach - void setUp() { - authInfo = AuthInfo.builder() - .sub("22222-34534-123") - .username("22222-34534-123") - .build(); - } @Test @DisplayName("새로운 게시글을 작성한다.") @@ -42,6 +40,8 @@ void addPost() { .postCategory("자유") .postContent("content") .build(); + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); postCreateService.addPost(authInfo, request); @@ -57,6 +57,9 @@ void addPost_exception_invalid_category() { .postContent("content") .build(); + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); + assertThatThrownBy(() -> postCreateService.addPost(authInfo, newPostRequest)) .isInstanceOf(InvalidPostCategoryException.class); } diff --git a/src/test/java/org/wooriverygood/api/post/application/PostDeleteServiceTest.java b/src/test/java/org/wooriverygood/api/post/application/PostDeleteServiceTest.java index 4d04c48..bb3a8c0 100644 --- a/src/test/java/org/wooriverygood/api/post/application/PostDeleteServiceTest.java +++ b/src/test/java/org/wooriverygood/api/post/application/PostDeleteServiceTest.java @@ -1,28 +1,26 @@ package org.wooriverygood.api.post.application; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.Mockito; import org.wooriverygood.api.global.error.exception.AuthorizationException; import org.wooriverygood.api.comment.repository.CommentRepository; +import org.wooriverygood.api.member.domain.Member; +import org.wooriverygood.api.member.repository.MemberRepository; import org.wooriverygood.api.post.domain.Post; -import org.wooriverygood.api.post.domain.PostCategory; import org.wooriverygood.api.post.repository.PostLikeRepository; import org.wooriverygood.api.post.repository.PostRepository; -import org.wooriverygood.api.global.auth.AuthInfo; -import org.wooriverygood.api.util.MockTest; -import java.util.ArrayList; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; -class PostDeleteServiceTest extends MockTest { +class PostDeleteServiceTest extends PostServiceTest { @InjectMocks private PostDeleteService postDeleteService; @@ -36,57 +34,42 @@ class PostDeleteServiceTest extends MockTest { @Mock private PostLikeRepository postLikeRepository; - private Post singlePost; - - private AuthInfo authInfo; - - @BeforeEach - void setUp() { - authInfo = AuthInfo.builder() - .sub("22222-34534-123") - .username("22222-34534-123") - .build(); + @Mock + private MemberRepository memberRepository; - singlePost = Post.builder() - .id(6L) - .category(PostCategory.OFFER) - .title("title6") - .content("content6") - .author(authInfo.getUsername()) - .comments(new ArrayList<>()) - .postLikes(new ArrayList<>()) - .build(); - } @Test @DisplayName("권한이 있는 게시글을 삭제한다.") void deletePost() { - Mockito.when(postRepository.findById(any(Long.class))) - .thenReturn(Optional.ofNullable(singlePost)); + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); + when(postRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(post)); - postDeleteService.deletePost(authInfo, singlePost.getId()); + postDeleteService.deletePost(authInfo, post.getId()); - verify(commentRepository).deleteAllByPost(singlePost); - verify(postLikeRepository).deleteAllByPost(singlePost); + assertAll( + () -> verify(commentRepository).deleteAllByPost(post), + () -> verify(postLikeRepository).deleteAllByPost(post) + ); } @Test - @DisplayName("권한이 없는 게시글을 삭제한다.") + @DisplayName("권한이 없는 게시글을 삭제하면 예외가 발생한다.") void deletePost_exception_noAuth() { Post noAuthPost = Post.builder() - .id(99L) - .category(PostCategory.OFFER) - .title("title99") - .content("content99") - .author("43434-45654-234") - .comments(new ArrayList<>()) - .postLikes(new ArrayList<>()) + .id(9L) + .title("title") + .content("content") + .member(new Member(5L, "username")) .build(); - Mockito.when(postRepository.findById(any(Long.class))) + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); + when(postRepository.findById(anyLong())) .thenReturn(Optional.ofNullable(noAuthPost)); - assertThatThrownBy(() -> postDeleteService.deletePost(authInfo, singlePost.getId())) + assertThatThrownBy(() -> postDeleteService.deletePost(authInfo, post.getId())) .isInstanceOf(AuthorizationException.class); } diff --git a/src/test/java/org/wooriverygood/api/post/application/PostFindServiceTest.java b/src/test/java/org/wooriverygood/api/post/application/PostFindServiceTest.java index 3d75646..6037831 100644 --- a/src/test/java/org/wooriverygood/api/post/application/PostFindServiceTest.java +++ b/src/test/java/org/wooriverygood/api/post/application/PostFindServiceTest.java @@ -1,6 +1,5 @@ package org.wooriverygood.api.post.application; -import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -9,6 +8,8 @@ import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; +import org.wooriverygood.api.member.domain.Member; +import org.wooriverygood.api.member.repository.MemberRepository; import org.wooriverygood.api.post.domain.Post; import org.wooriverygood.api.post.domain.PostCategory; import org.wooriverygood.api.post.dto.PostDetailResponse; @@ -16,19 +17,19 @@ import org.wooriverygood.api.post.exception.PostNotFoundException; import org.wooriverygood.api.post.repository.PostLikeRepository; import org.wooriverygood.api.post.repository.PostRepository; -import org.wooriverygood.api.global.auth.AuthInfo; -import org.wooriverygood.api.util.MockTest; import java.util.ArrayList; import java.util.List; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.when; - -class PostFindServiceTest extends MockTest { +class PostFindServiceTest extends PostServiceTest { @InjectMocks private PostFindService postFindService; @@ -39,30 +40,30 @@ class PostFindServiceTest extends MockTest { @Mock private PostLikeRepository postLikeRepository; + @Mock + private MemberRepository memberRepository; + private List posts = new ArrayList<>(); private List myPosts = new ArrayList<>(); private List freePosts = new ArrayList<>(); - private AuthInfo authInfo; - private final int POST_COUNT = 23; + @BeforeEach void setUp() { - authInfo = AuthInfo.builder() - .sub("22222-34534-123") - .username("22222-34534-123") - .build(); - + posts.clear(); + myPosts.clear(); + freePosts.clear(); for (long i = 1; i <= POST_COUNT; i++) { Post post = Post.builder() .id(i) .category(PostCategory.FREE) .title("title" + i) .content("content" + i) - .author(authInfo.getUsername()) + .member(member) .comments(new ArrayList<>()) .postLikes(new ArrayList<>()) .build(); @@ -75,7 +76,7 @@ void setUp() { } @Test - @DisplayName("로그인 한 상황에서 게시글을 불러온다.") + @DisplayName("로그인 한 상황에서 모든 카테고리의 게시글을 불러온다.") void findPosts_login() { Pageable pageable = PageRequest.of(0, 10); @@ -85,12 +86,18 @@ void findPosts_login() { PageImpl page = new PageImpl<>(posts, pageable, this.posts.size()); when(postRepository.findAllByOrderByIdDesc(any(PageRequest.class))) .thenReturn(page); + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); + when(postLikeRepository.existsByPostAndMember(any(Post.class), any(Member.class))) + .thenReturn(true); PostsResponse response = postFindService.findPosts(authInfo, pageable, ""); - assertThat(response.getPosts().size()).isEqualTo(10); - assertThat(response.getTotalPageCount()).isEqualTo(3); - assertThat(response.getTotalPostCount()).isEqualTo(POST_COUNT); + assertAll( + () -> assertThat(response.getPosts().size()).isEqualTo(10), + () -> assertThat(response.getTotalPageCount()).isEqualTo(3), + () -> assertThat(response.getTotalPostCount()).isEqualTo(POST_COUNT) + ); } @Test @@ -103,32 +110,29 @@ void findPosts_category_free() { PageImpl page = new PageImpl<>(posts, pageable, this.freePosts.size()); when(postRepository.findAllByCategoryOrderByIdDesc(any(PostCategory.class), any(PageRequest.class))) .thenReturn(page); + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); PostsResponse response = postFindService.findPosts(authInfo, pageable, "자유"); - assertThat(response.getPosts().size()).isEqualTo(10); - assertThat(response.getTotalPageCount()).isEqualTo(2); - assertThat(response.getTotalPostCount()).isEqualTo(14); + assertAll( + () -> assertThat(response.getPosts().size()).isEqualTo(10), + () -> assertThat(response.getTotalPageCount()).isEqualTo(2), + () -> assertThat(response.getTotalPostCount()).isEqualTo(14) + ); } @Test @DisplayName("유효한 id를 이용하여 특정 게시글을 불러온다.") void findPostById() { - Post singlePost = Post.builder() - .id(6L) - .category(PostCategory.OFFER) - .title("title6") - .content("content6") - .author(authInfo.getUsername()) - .comments(new ArrayList<>()) - .postLikes(new ArrayList<>()) - .build(); - when(postRepository.findById(any())) - .thenReturn(Optional.ofNullable(singlePost)); + when(postRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(post)); + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); PostDetailResponse response = postFindService.findPostById(6L, authInfo); - assertThat(response).isNotNull(); + assertThat(response.getPostId()).isEqualTo(post.getId()); } @Test @@ -137,7 +141,7 @@ void findPostById_exception_invalidId() { when(postRepository.findById(any())) .thenThrow(PostNotFoundException.class); - Assertions.assertThatThrownBy(() -> postFindService.findPostById(6L, authInfo)) + assertThatThrownBy(() -> postFindService.findPostById(6L, authInfo)) .isInstanceOf(PostNotFoundException.class); } @@ -152,12 +156,16 @@ void findMyPosts() { PageImpl page = new PageImpl<>(posts, pageable, this.myPosts.size()); when(postRepository.findByAuthorOrderByIdDesc(any(String.class), any(PageRequest.class))) .thenReturn(page); + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); PostsResponse response = postFindService.findMyPosts(authInfo, pageable); - assertThat(response.getPosts().size()).isEqualTo(10); - assertThat(response.getTotalPageCount()).isEqualTo(2); - assertThat(response.getTotalPostCount()).isEqualTo(14); + assertAll( + () -> assertThat(response.getPosts().size()).isEqualTo(10), + () -> assertThat(response.getTotalPageCount()).isEqualTo(2), + () -> assertThat(response.getTotalPostCount()).isEqualTo(14) + ); } } \ No newline at end of file diff --git a/src/test/java/org/wooriverygood/api/post/application/PostLikeToggleServiceTest.java b/src/test/java/org/wooriverygood/api/post/application/PostLikeToggleServiceTest.java index ef265ce..749cc90 100644 --- a/src/test/java/org/wooriverygood/api/post/application/PostLikeToggleServiceTest.java +++ b/src/test/java/org/wooriverygood/api/post/application/PostLikeToggleServiceTest.java @@ -1,26 +1,25 @@ package org.wooriverygood.api.post.application; -import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.wooriverygood.api.member.domain.Member; +import org.wooriverygood.api.member.repository.MemberRepository; import org.wooriverygood.api.post.domain.Post; -import org.wooriverygood.api.post.domain.PostCategory; import org.wooriverygood.api.post.domain.PostLike; import org.wooriverygood.api.post.dto.PostLikeResponse; import org.wooriverygood.api.post.repository.PostLikeRepository; import org.wooriverygood.api.post.repository.PostRepository; -import org.wooriverygood.api.global.auth.AuthInfo; -import org.wooriverygood.api.util.MockTest; -import java.util.ArrayList; import java.util.Optional; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.when; -class PostLikeToggleServiceTest extends MockTest { +class PostLikeToggleServiceTest extends PostServiceTest { @InjectMocks private PostLikeToggleService postLikeToggleService; @@ -31,31 +30,26 @@ class PostLikeToggleServiceTest extends MockTest { @Mock private PostLikeRepository postLikeRepository; - private AuthInfo authInfo = AuthInfo.builder() - .sub("") - .username("22222-34534-123") - .build(); - - private Post post = Post.builder() - .id(6L) - .category(PostCategory.OFFER) - .title("title6") - .content("content6") - .author(authInfo.getUsername()) - .comments(new ArrayList<>()) - .postLikes(new ArrayList<>()) - .build(); + @Mock + private MemberRepository memberRepository; + @Test @DisplayName("특정 게시글에 좋아요를 1 올린다.") void likePost_up() { + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); when(postRepository.findById(anyLong())) .thenReturn(Optional.ofNullable(post)); + when(postLikeRepository.findByPostAndMember(any(Post.class), any(Member.class))) + .thenReturn(Optional.empty()); PostLikeResponse response = postLikeToggleService.togglePostLike(post.getId(), authInfo); - Assertions.assertThat(response.getLikeCount()).isEqualTo(post.getLikeCount() + 1); - Assertions.assertThat(response.isLiked()).isEqualTo(true); + assertAll( + () -> assertThat(response.getLikeCount()).isEqualTo(post.getLikeCount() + 1), + () -> assertThat(response.isLiked()).isEqualTo(true) + ); } @Test @@ -66,15 +60,19 @@ void likePost_down() { .post(post) .username(authInfo.getUsername()) .build(); + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); when(postRepository.findById(anyLong())) .thenReturn(Optional.ofNullable(post)); - when(postLikeRepository.findByPostAndUsername(any(Post.class), anyString())) + when(postLikeRepository.findByPostAndMember(any(Post.class), any(Member.class))) .thenReturn(Optional.ofNullable(postLike)); PostLikeResponse response = postLikeToggleService.togglePostLike(postLike.getId(), authInfo); - Assertions.assertThat(response.getLikeCount()).isEqualTo(post.getLikeCount() - 1); - Assertions.assertThat(response.isLiked()).isEqualTo(false); + assertAll( + () -> assertThat(response.getLikeCount()).isEqualTo(post.getLikeCount() - 1), + () -> assertThat(response.isLiked()).isEqualTo(false) + ); } } \ No newline at end of file diff --git a/src/test/java/org/wooriverygood/api/post/application/PostServiceTest.java b/src/test/java/org/wooriverygood/api/post/application/PostServiceTest.java new file mode 100644 index 0000000..661ed89 --- /dev/null +++ b/src/test/java/org/wooriverygood/api/post/application/PostServiceTest.java @@ -0,0 +1,27 @@ +package org.wooriverygood.api.post.application; + +import org.junit.jupiter.api.BeforeEach; +import org.wooriverygood.api.post.domain.Post; +import org.wooriverygood.api.post.domain.PostCategory; +import org.wooriverygood.api.util.MockTest; + +import java.util.ArrayList; + +public class PostServiceTest extends MockTest { + + protected Post post; + + @BeforeEach + void dateSetUp() { + post = Post.builder() + .id(1L) + .category(PostCategory.OFFER) + .title("simple title") + .content("simple content") + .member(member) + .comments(new ArrayList<>()) + .postLikes(new ArrayList<>()) + .build(); + } + +} diff --git a/src/test/java/org/wooriverygood/api/post/application/PostUpdateServiceTest.java b/src/test/java/org/wooriverygood/api/post/application/PostUpdateServiceTest.java index 70c113d..22a5b59 100644 --- a/src/test/java/org/wooriverygood/api/post/application/PostUpdateServiceTest.java +++ b/src/test/java/org/wooriverygood/api/post/application/PostUpdateServiceTest.java @@ -1,25 +1,27 @@ package org.wooriverygood.api.post.application; -import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; import org.wooriverygood.api.global.error.exception.AuthorizationException; +import org.wooriverygood.api.member.domain.Member; +import org.wooriverygood.api.member.repository.MemberRepository; import org.wooriverygood.api.post.domain.Post; import org.wooriverygood.api.post.domain.PostCategory; import org.wooriverygood.api.post.dto.PostUpdateRequest; import org.wooriverygood.api.post.repository.PostRepository; -import org.wooriverygood.api.global.auth.AuthInfo; -import org.wooriverygood.api.util.MockTest; import java.util.ArrayList; import java.util.Optional; -import static org.mockito.ArgumentMatchers.any; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.when; -class PostUpdateServiceTest extends MockTest { +class PostUpdateServiceTest extends PostServiceTest { @InjectMocks private PostUpdateService postUpdateService; @@ -27,32 +29,20 @@ class PostUpdateServiceTest extends MockTest { @Mock private PostRepository postRepository; - private AuthInfo authInfo = AuthInfo.builder() - .sub("") - .username("22222-34534-123") - .build(); - @Mock - private Post post = Post.builder() - .id(6L) - .category(PostCategory.OFFER) - .title("title6") - .content("content6") - .author(authInfo.getUsername()) - .comments(new ArrayList<>()) - .postLikes(new ArrayList<>()) - .build(); + private MemberRepository memberRepository; private Post noAuthPost = Post.builder() .id(99L) .category(PostCategory.OFFER) .title("title99") .content("content99") - .author("43434-45654-234") + .member(new Member(5L, "username")) .comments(new ArrayList<>()) .postLikes(new ArrayList<>()) .build(); + @Test @DisplayName("권한이 있는 게시글을 수정한다.") void updatePost() { @@ -60,12 +50,17 @@ void updatePost() { .postTitle("new title") .postContent("new content") .build(); - - when(postRepository.findById(any(Long.class))) + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); + when(postRepository.findById(anyLong())) .thenReturn(Optional.ofNullable(post)); postUpdateService.updatePost(post.getId(), request, authInfo); + assertAll( + () -> assertThat(post.getTitle()).isEqualTo(request.getPostTitle()), + () -> assertThat(post.getContent()).isEqualTo(request.getPostContent()) + ); } @Test @@ -75,11 +70,12 @@ void updatePost_exception_noAuth() { .postTitle("new title") .postContent("new content") .build(); - - when(postRepository.findById(any(Long.class))) + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); + when(postRepository.findById(anyLong())) .thenReturn(Optional.ofNullable(noAuthPost)); - Assertions.assertThatThrownBy(() -> postUpdateService.updatePost(noAuthPost.getId(), request, authInfo)) + assertThatThrownBy(() -> postUpdateService.updatePost(noAuthPost.getId(), request, authInfo)) .isInstanceOf(AuthorizationException.class); } diff --git a/src/test/java/org/wooriverygood/api/report/application/CommentReportServiceTest.java b/src/test/java/org/wooriverygood/api/report/application/CommentReportServiceTest.java index 47988c3..ce54bd3 100644 --- a/src/test/java/org/wooriverygood/api/report/application/CommentReportServiceTest.java +++ b/src/test/java/org/wooriverygood/api/report/application/CommentReportServiceTest.java @@ -1,10 +1,14 @@ package org.wooriverygood.api.report.application; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; import org.wooriverygood.api.comment.exception.CommentNotFoundException; +import org.wooriverygood.api.member.repository.MemberRepository; +import org.wooriverygood.api.post.domain.Post; +import org.wooriverygood.api.post.domain.PostCategory; import org.wooriverygood.api.report.domain.CommentReport; import org.wooriverygood.api.report.exception.DuplicatedCommentReportException; import org.wooriverygood.api.comment.domain.Comment; @@ -14,9 +18,12 @@ import org.wooriverygood.api.global.auth.AuthInfo; import org.wooriverygood.api.util.MockTest; +import java.util.ArrayList; import java.util.Optional; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -32,53 +39,64 @@ class CommentReportServiceTest extends MockTest { @Mock private CommentReportRepository commentReportRepository; - private AuthInfo authInfo = AuthInfo.builder() - .sub("22432-12312-3531") - .username("22432-12312-3531") - .build(); - @Mock + private MemberRepository memberRepository; + + private Post post; + private Comment comment; + @BeforeEach + void setUp() { + post = Post.builder() + .id(6L) + .category(PostCategory.OFFER) + .title("title6") + .content("content6") + .member(member) + .comments(new ArrayList<>()) + .postLikes(new ArrayList<>()) + .build(); + comment = Comment.builder() + .id(1L) + .content("Parent") + .author("author1") + .post(post) + .member(member) + .reports(new ArrayList<>()) + .build(); + } + @Test @DisplayName("유효한 id를 통해 특정 댓글을 신고한다.") void reportComment() { ReportRequest request = new ReportRequest("report message"); - when(comment.hasReportByUser(anyString())) - .thenReturn(false); + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); when(commentRepository.findById(anyLong())) .thenReturn(Optional.ofNullable(comment)); - commentReportService.reportComment(1L, request, authInfo); - - verify(comment).hasReportByUser(authInfo.getUsername()); - verify(comment).addReport(any(CommentReport.class)); - verify(commentRepository).increaseReportCount(anyLong()); - verify(commentReportRepository).save(any(CommentReport.class)); - } - - @Test - @DisplayName("신고하려는 댓글의 id가 유효하지 않으면 예외를 발생한다.") - void reportComment_exception_invalidId() { - ReportRequest request = new ReportRequest("report message"); + commentReportService.reportComment(comment.getId(), request, authInfo); - when(commentRepository.findById(anyLong())) - .thenThrow(new CommentNotFoundException()); - - assertThatThrownBy(() -> commentReportService.reportComment(1L, request, authInfo)) - .isInstanceOf(CommentNotFoundException.class); + assertAll( + () -> assertThat(comment.getReports().size()).isEqualTo(1), + () -> verify(commentRepository).increaseReportCount(comment.getId()), + () -> verify(commentReportRepository).save(any(CommentReport.class)) + ); } @Test @DisplayName("동일한 댓글을 한 번 이상 신고하면 예외를 발생한다.") void reportComment_exception_duplicated() { ReportRequest request = new ReportRequest("report message"); - when(comment.hasReportByUser(anyString())) - .thenReturn(true); + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); when(commentRepository.findById(anyLong())) .thenReturn(Optional.ofNullable(comment)); + commentReportService.reportComment(comment.getId(), request, authInfo); + assertThatThrownBy(() -> commentReportService.reportComment(1L, request, authInfo)) .isInstanceOf(DuplicatedCommentReportException.class); } diff --git a/src/test/java/org/wooriverygood/api/report/application/PostReportServiceTest.java b/src/test/java/org/wooriverygood/api/report/application/PostReportServiceTest.java index d778050..8223f08 100644 --- a/src/test/java/org/wooriverygood/api/report/application/PostReportServiceTest.java +++ b/src/test/java/org/wooriverygood/api/report/application/PostReportServiceTest.java @@ -5,6 +5,8 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.wooriverygood.api.global.auth.AuthInfo; +import org.wooriverygood.api.member.domain.Member; +import org.wooriverygood.api.post.application.PostServiceTest; import org.wooriverygood.api.post.domain.Post; import org.wooriverygood.api.post.exception.PostNotFoundException; import org.wooriverygood.api.post.repository.PostRepository; @@ -21,7 +23,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -class PostReportServiceTest extends MockTest { +class PostReportServiceTest extends PostServiceTest { @InjectMocks private PostReportService postReportService; @@ -32,11 +34,6 @@ class PostReportServiceTest extends MockTest { @Mock private PostReportRepository postReportRepository; - private AuthInfo authInfo = AuthInfo.builder() - .sub("22432-12312-3531") - .username("22432-12312-3531") - .build(); - @Mock private Post post; @@ -45,14 +42,14 @@ class PostReportServiceTest extends MockTest { @DisplayName("유효한 id를 통해 특정 게시글을 신고한다.") void reportPost() { ReportRequest request = new ReportRequest("report message"); - when(post.hasReportByUser(anyString())) + when(post.hasReportByMember(any(Member.class))) .thenReturn(false); when(postRepository.findById(anyLong())) .thenReturn(Optional.ofNullable(post)); postReportService.reportPost(1L, request, authInfo); - verify(post).hasReportByUser(authInfo.getUsername()); + verify(post).hasReportByMember(member); verify(post).addReport(any(PostReport.class)); verify(postRepository).increaseReportCount(anyLong()); verify(postReportRepository).save(any(PostReport.class)); @@ -74,7 +71,7 @@ void reportPost_exception_invalidId() { @DisplayName("동일한 게시글을 한 번 이상 신고하면 예외를 발생한다.") void reportPost_exception_duplicated() { ReportRequest request = new ReportRequest("report message"); - when(post.hasReportByUser(anyString())) + when(post.hasReportByMember(any(Member.class))) .thenReturn(true); when(postRepository.findById(anyLong())) .thenReturn(Optional.ofNullable(post)); diff --git a/src/test/java/org/wooriverygood/api/util/MockTest.java b/src/test/java/org/wooriverygood/api/util/MockTest.java index 36217d3..a4aae7c 100644 --- a/src/test/java/org/wooriverygood/api/util/MockTest.java +++ b/src/test/java/org/wooriverygood/api/util/MockTest.java @@ -1,5 +1,6 @@ package org.wooriverygood.api.util; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; import org.wooriverygood.api.global.auth.AuthInfo; @@ -14,6 +15,12 @@ public class MockTest { .username("22222-34534-123") .build(); - protected Member member = new Member(1L, authInfo.getUsername()); + protected Member member; + + + @BeforeEach + void mockData() { + member = new Member(1L, authInfo.getUsername()); + } } From c196ba696249760b34d0bf303cb9244c2b0986ba Mon Sep 17 00:00:00 2001 From: Park Geonho Date: Sun, 28 Apr 2024 23:46:12 +0800 Subject: [PATCH 18/21] =?UTF-8?q?[fix]=20Comment=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=EC=97=90=20Member=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/CommentCreateService.java | 14 +++- .../application/CommentDeleteService.java | 13 ++- .../application/CommentFindService.java | 25 +++--- .../application/CommentLikeToggleService.java | 15 +++- .../application/CommentUpdateService.java | 9 ++- .../api/comment/domain/Comment.java | 16 ++-- .../api/comment/domain/CommentLike.java | 8 +- .../api/comment/dto/CommentResponse.java | 26 ++++-- .../api/comment/dto/ReplyResponse.java | 21 +++-- .../repository/CommentLikeRepository.java | 5 +- .../api/course/domain/Course.java | 4 +- .../api/comment/api/CommentApiTest.java | 7 +- .../application/CommentCreateServiceTest.java | 51 ++++-------- .../application/CommentDeleteServiceTest.java | 80 +++++++++---------- .../application/CommentFindServiceTest.java | 56 +++++-------- .../CommentLikeToggleServiceTest.java | 74 ++++++----------- .../application/CommentServiceTest.java | 29 +++++++ .../application/CommentUpdateServiceTest.java | 42 ++++------ .../api/post/api/PostApiTest.java | 4 + .../application/CommentReportServiceTest.java | 8 -- .../api/util/ResponseFixture.java | 4 + 21 files changed, 260 insertions(+), 251 deletions(-) diff --git a/src/main/java/org/wooriverygood/api/comment/application/CommentCreateService.java b/src/main/java/org/wooriverygood/api/comment/application/CommentCreateService.java index e28507c..60c338e 100644 --- a/src/main/java/org/wooriverygood/api/comment/application/CommentCreateService.java +++ b/src/main/java/org/wooriverygood/api/comment/application/CommentCreateService.java @@ -10,6 +10,9 @@ import org.wooriverygood.api.comment.dto.NewReplyRequest; import org.wooriverygood.api.comment.repository.CommentRepository; import org.wooriverygood.api.global.auth.AuthInfo; +import org.wooriverygood.api.member.domain.Member; +import org.wooriverygood.api.member.exception.MemberNotFoundException; +import org.wooriverygood.api.member.repository.MemberRepository; import org.wooriverygood.api.post.domain.Post; import org.wooriverygood.api.post.exception.PostNotFoundException; import org.wooriverygood.api.post.repository.PostRepository; @@ -23,15 +26,19 @@ public class CommentCreateService { private final CommentRepository commentRepository; + private final MemberRepository memberRepository; + public void addComment(AuthInfo authInfo, Long postId, NewCommentRequest request) { + Member member = memberRepository.findById(authInfo.getMemberId()) + .orElseThrow(MemberNotFoundException::new); Post post = postRepository.findById(postId) .orElseThrow(PostNotFoundException::new); Comment comment = Comment.builder() .content(request.getContent()) - .author(authInfo.getUsername()) .post(post) + .member(member) .build(); commentRepository.save(comment); @@ -44,11 +51,14 @@ public void addReply(Long commentId, NewReplyRequest request, AuthInfo authInfo) if (!parent.isParent()) throw new ReplyDepthException(); + Member member = memberRepository.findById(authInfo.getMemberId()) + .orElseThrow(MemberNotFoundException::new); + Comment child = Comment.builder() .content(request.getContent()) - .author(authInfo.getUsername()) .post(parent.getPost()) .parent(parent) + .member(member) .build(); parent.addReply(child); diff --git a/src/main/java/org/wooriverygood/api/comment/application/CommentDeleteService.java b/src/main/java/org/wooriverygood/api/comment/application/CommentDeleteService.java index 826b8bd..4bab334 100644 --- a/src/main/java/org/wooriverygood/api/comment/application/CommentDeleteService.java +++ b/src/main/java/org/wooriverygood/api/comment/application/CommentDeleteService.java @@ -8,6 +8,9 @@ import org.wooriverygood.api.comment.repository.CommentLikeRepository; import org.wooriverygood.api.comment.repository.CommentRepository; import org.wooriverygood.api.global.auth.AuthInfo; +import org.wooriverygood.api.member.domain.Member; +import org.wooriverygood.api.member.exception.MemberNotFoundException; +import org.wooriverygood.api.member.repository.MemberRepository; @Service @Transactional @@ -18,12 +21,16 @@ public class CommentDeleteService { private final CommentLikeRepository commentLikeRepository; + private final MemberRepository memberRepository; + public void deleteComment(Long commentId, AuthInfo authInfo) { + Member member = memberRepository.findById(authInfo.getMemberId()) + .orElseThrow(MemberNotFoundException::new); Comment comment = commentRepository.findById(commentId) .orElseThrow(CommentNotFoundException::new); - comment.validateAuthor(authInfo.getUsername()); + comment.validateAuthor(member); commentLikeRepository.deleteAllByComment(comment); deleteCommentOrReply(comment); @@ -34,7 +41,7 @@ private void deleteCommentOrReply(Comment comment) { deleteParent(comment); return; } - deleteChild(comment); + deleteReply(comment); } private void deleteParent(Comment parent) { @@ -45,7 +52,7 @@ private void deleteParent(Comment parent) { parent.willBeDeleted(); } - private void deleteChild(Comment reply) { + private void deleteReply(Comment reply) { Comment parent = reply.getParent(); parent.deleteReply(reply); commentRepository.delete(reply); diff --git a/src/main/java/org/wooriverygood/api/comment/application/CommentFindService.java b/src/main/java/org/wooriverygood/api/comment/application/CommentFindService.java index c6f09a0..599a19a 100644 --- a/src/main/java/org/wooriverygood/api/comment/application/CommentFindService.java +++ b/src/main/java/org/wooriverygood/api/comment/application/CommentFindService.java @@ -10,45 +10,52 @@ import org.wooriverygood.api.comment.repository.CommentLikeRepository; import org.wooriverygood.api.comment.repository.CommentRepository; import org.wooriverygood.api.global.auth.AuthInfo; +import org.wooriverygood.api.member.domain.Member; +import org.wooriverygood.api.member.exception.MemberNotFoundException; +import org.wooriverygood.api.member.repository.MemberRepository; import java.util.List; import java.util.Objects; @Service -@Transactional(readOnly = true) @RequiredArgsConstructor +@Transactional(readOnly = true) public class CommentFindService { private final CommentRepository commentRepository; private final CommentLikeRepository commentLikeRepository; + private final MemberRepository memberRepository; + public CommentsResponse findAllCommentsByPostId(Long postId, AuthInfo authInfo) { + Member member = memberRepository.findById(authInfo.getMemberId()) + .orElseThrow(MemberNotFoundException::new); List comments = commentRepository.findAllByPostId(postId); List responses = comments.stream() - .map(comment -> convertToCommentResponse(comment, authInfo)) + .map(comment -> convertToCommentResponse(comment, member)) .filter(response -> !Objects.isNull(response)) .toList(); return new CommentsResponse(responses); } - private CommentResponse convertToCommentResponse(Comment comment, AuthInfo authInfo) { + private CommentResponse convertToCommentResponse(Comment comment, Member member) { if (comment.isReply()) return null; if (comment.isSoftRemoved()) - return CommentResponse.softRemovedOf(comment, convertToReplyResponses(comment, authInfo)); + return CommentResponse.softRemovedOf(comment, convertToReplyResponses(comment, member), comment.sameAuthor(member)); - boolean liked = commentLikeRepository.existsByCommentAndUsername(comment, authInfo.getUsername()); - return CommentResponse.of(comment, convertToReplyResponses(comment, authInfo), liked); + boolean liked = commentLikeRepository.existsByCommentAndMember(comment, member); + return CommentResponse.of(comment, convertToReplyResponses(comment, member), liked, comment.sameAuthor(member)); } - private List convertToReplyResponses(Comment parent, AuthInfo authInfo) { + private List convertToReplyResponses(Comment parent, Member member) { return parent.getReplies() .stream() .map(reply -> { - boolean liked = commentLikeRepository.existsByCommentAndUsername(reply, authInfo.getUsername()); - return ReplyResponse.from(reply, liked); + boolean liked = commentLikeRepository.existsByCommentAndMember(reply, member); + return ReplyResponse.of(reply, liked, reply.sameAuthor(member)); }) .toList(); } diff --git a/src/main/java/org/wooriverygood/api/comment/application/CommentLikeToggleService.java b/src/main/java/org/wooriverygood/api/comment/application/CommentLikeToggleService.java index 9abd67d..e05aef3 100644 --- a/src/main/java/org/wooriverygood/api/comment/application/CommentLikeToggleService.java +++ b/src/main/java/org/wooriverygood/api/comment/application/CommentLikeToggleService.java @@ -10,6 +10,9 @@ import org.wooriverygood.api.comment.repository.CommentRepository; import org.wooriverygood.api.comment.exception.CommentNotFoundException; import org.wooriverygood.api.global.auth.AuthInfo; +import org.wooriverygood.api.member.domain.Member; +import org.wooriverygood.api.member.exception.MemberNotFoundException; +import org.wooriverygood.api.member.repository.MemberRepository; import java.util.Optional; @@ -22,15 +25,19 @@ public class CommentLikeToggleService { private final CommentLikeRepository commentLikeRepository; + private final MemberRepository memberRepository; + public CommentLikeResponse likeComment(Long commentId, AuthInfo authInfo) { + Member member = memberRepository.findById(authInfo.getMemberId()) + .orElseThrow(MemberNotFoundException::new); Comment comment = commentRepository.findById(commentId) .orElseThrow(CommentNotFoundException::new); - Optional commentLike = commentLikeRepository.findByCommentAndUsername(comment, authInfo.getUsername()); + Optional commentLike = commentLikeRepository.findByCommentAndMember(comment, member); if (commentLike.isEmpty()) { - addCommentLike(comment, authInfo.getUsername()); + addCommentLike(comment, member); return createCommentLikeResponse(comment, true); } @@ -38,10 +45,10 @@ public CommentLikeResponse likeComment(Long commentId, AuthInfo authInfo) { return createCommentLikeResponse(comment, false); } - private void addCommentLike(Comment comment, String username) { + private void addCommentLike(Comment comment, Member member) { CommentLike commentLike = CommentLike.builder() .comment(comment) - .username(username) + .member(member) .build(); comment.addCommentLike(commentLike); diff --git a/src/main/java/org/wooriverygood/api/comment/application/CommentUpdateService.java b/src/main/java/org/wooriverygood/api/comment/application/CommentUpdateService.java index 6ddca9e..20e8e4a 100644 --- a/src/main/java/org/wooriverygood/api/comment/application/CommentUpdateService.java +++ b/src/main/java/org/wooriverygood/api/comment/application/CommentUpdateService.java @@ -8,6 +8,9 @@ import org.wooriverygood.api.comment.dto.CommentUpdateRequest; import org.wooriverygood.api.comment.repository.CommentRepository; import org.wooriverygood.api.global.auth.AuthInfo; +import org.wooriverygood.api.member.domain.Member; +import org.wooriverygood.api.member.exception.MemberNotFoundException; +import org.wooriverygood.api.member.repository.MemberRepository; @Service @Transactional @@ -16,12 +19,16 @@ public class CommentUpdateService { private final CommentRepository commentRepository; + private final MemberRepository memberRepository; + public void updateComment(Long commentId, CommentUpdateRequest request, AuthInfo authInfo) { + Member member = memberRepository.findById(authInfo.getMemberId()) + .orElseThrow(MemberNotFoundException::new); Comment comment = commentRepository.findById(commentId) .orElseThrow(CommentNotFoundException::new); - comment.validateAuthor(authInfo.getUsername()); + comment.validateAuthor(member); comment.updateContent(request.getContent()); } diff --git a/src/main/java/org/wooriverygood/api/comment/domain/Comment.java b/src/main/java/org/wooriverygood/api/comment/domain/Comment.java index 70b9b9e..d5a4b0f 100644 --- a/src/main/java/org/wooriverygood/api/comment/domain/Comment.java +++ b/src/main/java/org/wooriverygood/api/comment/domain/Comment.java @@ -37,9 +37,6 @@ public class Comment { @Column(name = "comment_content", length = 200, nullable = false) private String content; - @Column(name = "comment_author", length = 1000) - private String author; - @ManyToOne(fetch = FetchType.LAZY) private Member member; @@ -73,12 +70,11 @@ public class Comment { private boolean updated; @Builder - public Comment(Long id, String content, String author, Post post, - Member member, Comment parent, List commentLikes, + public Comment(Long id, String content, Post post, Member member, + Comment parent, List commentLikes, List reports, boolean softRemoved, boolean updated) { this.id = id; this.content = content; - this.author = author; this.post = post; this.parent = parent; this.commentLikes = commentLikes; @@ -108,8 +104,12 @@ public boolean hasReportByMember(Member member) { return false; } - public void validateAuthor(String author) { - if (!this.author.equals(author)) throw new AuthorizationException(); + public boolean sameAuthor(Member member) { + return this.member.equals(member); + } + + public void validateAuthor(Member member) { + if (!sameAuthor(member)) throw new AuthorizationException(); } public void updateContent(String content) { diff --git a/src/main/java/org/wooriverygood/api/comment/domain/CommentLike.java b/src/main/java/org/wooriverygood/api/comment/domain/CommentLike.java index 7890f17..5b9a741 100644 --- a/src/main/java/org/wooriverygood/api/comment/domain/CommentLike.java +++ b/src/main/java/org/wooriverygood/api/comment/domain/CommentLike.java @@ -4,6 +4,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.wooriverygood.api.member.domain.Member; @Entity @Table(name = "commentLikes") @@ -19,14 +20,15 @@ public class CommentLike { @JoinColumn(name = "comment_id", referencedColumnName = "comment_id") private Comment comment; - private String username; + @ManyToOne(fetch = FetchType.LAZY) + private Member member; @Builder - public CommentLike(Long id, Comment comment, String username) { + public CommentLike(Long id, Comment comment, Member member) { this.id = id; this.comment = comment; - this.username = username; + this.member = member; } public void delete() { diff --git a/src/main/java/org/wooriverygood/api/comment/dto/CommentResponse.java b/src/main/java/org/wooriverygood/api/comment/dto/CommentResponse.java index 91b790b..d610aa6 100644 --- a/src/main/java/org/wooriverygood/api/comment/dto/CommentResponse.java +++ b/src/main/java/org/wooriverygood/api/comment/dto/CommentResponse.java @@ -17,8 +17,6 @@ public class CommentResponse { private final String commentContent; - private final String commentAuthor; - private final Long postId; private final int commentLikeCount; @@ -33,12 +31,20 @@ public class CommentResponse { private final boolean reported; + private final boolean isMine; + + private final long memberId; + @Builder - public CommentResponse(Long commentId, String commentContent, String commentAuthor, Long postId, int commentLikeCount, LocalDateTime commentTime, boolean liked, List replies, boolean updated, boolean reported) { + public CommentResponse( + Long commentId, String commentContent, Long postId, + int commentLikeCount, LocalDateTime commentTime, boolean liked, + List replies, boolean updated, boolean reported, + boolean isMine, long memberId + ) { this.commentId = commentId; this.commentContent = commentContent; - this.commentAuthor = commentAuthor; this.postId = postId; this.commentLikeCount = commentLikeCount; this.commentTime = commentTime; @@ -46,14 +52,16 @@ public CommentResponse(Long commentId, String commentContent, String commentAuth this.replies = replies; this.updated = updated; this.reported = reported; + this.isMine = isMine; + this.memberId = memberId; } - public static CommentResponse of(Comment comment, List replies, boolean liked) { + public static CommentResponse of(Comment comment, List replies, boolean liked, boolean isMine) { return CommentResponse.builder() .commentId(comment.getId()) .commentContent(comment.getContent()) - .commentAuthor(comment.getAuthor()) + .memberId(comment.getMember().getId()) .postId(comment.getPost().getId()) .commentLikeCount(comment.getLikeCount()) .commentTime(comment.getCreatedAt()) @@ -61,20 +69,22 @@ public static CommentResponse of(Comment comment, List replies, b .replies(replies) .updated(comment.isUpdated()) .reported(comment.isReportedTooMuch()) + .isMine(isMine) .build(); } - public static CommentResponse softRemovedOf(Comment comment, List replies) { + public static CommentResponse softRemovedOf(Comment comment, List replies, boolean isMine) { return CommentResponse.builder() .commentId(comment.getId()) .commentContent(null) - .commentAuthor(comment.getAuthor()) + .memberId(comment.getMember().getId()) .postId(comment.getPost().getId()) .commentLikeCount(comment.getLikeCount()) .commentTime(comment.getCreatedAt()) .replies(replies) .updated(comment.isUpdated()) .reported(comment.isReportedTooMuch()) + .isMine(isMine) .build(); } diff --git a/src/main/java/org/wooriverygood/api/comment/dto/ReplyResponse.java b/src/main/java/org/wooriverygood/api/comment/dto/ReplyResponse.java index c9a5566..7556f1a 100644 --- a/src/main/java/org/wooriverygood/api/comment/dto/ReplyResponse.java +++ b/src/main/java/org/wooriverygood/api/comment/dto/ReplyResponse.java @@ -16,8 +16,6 @@ public class ReplyResponse { private final String replyContent; - private final String replyAuthor; - private final int replyLikeCount; private final LocalDateTime replyTime; @@ -28,13 +26,21 @@ public class ReplyResponse { private final boolean reported; + private final boolean isMine; - @Builder + private final long memberId; - public ReplyResponse(Long replyId, String replyContent, String replyAuthor, int replyLikeCount, LocalDateTime replyTime, boolean liked, boolean updated, boolean reported) { + + @Builder + public ReplyResponse( + Long replyId, String replyContent, int replyLikeCount, + LocalDateTime replyTime, boolean liked, boolean updated, + boolean reported, boolean isMine, long memberId + ) { this.replyId = replyId; this.replyContent = replyContent; - this.replyAuthor = replyAuthor; + this.isMine = isMine; + this.memberId = memberId; this.replyLikeCount = replyLikeCount; this.replyTime = replyTime; this.liked = liked; @@ -42,16 +48,17 @@ public ReplyResponse(Long replyId, String replyContent, String replyAuthor, int this.reported = reported; } - public static ReplyResponse from(Comment reply, boolean liked) { + public static ReplyResponse of(Comment reply, boolean liked, boolean isMine) { return ReplyResponse.builder() .replyId(reply.getId()) .replyContent(reply.getContent()) - .replyAuthor(reply.getAuthor()) + .memberId(reply.getMember().getId()) .replyLikeCount(reply.getLikeCount()) .replyTime(reply.getCreatedAt()) .liked(liked) .updated(reply.isUpdated()) .reported(reply.isReportedTooMuch()) + .isMine(isMine) .build(); } diff --git a/src/main/java/org/wooriverygood/api/comment/repository/CommentLikeRepository.java b/src/main/java/org/wooriverygood/api/comment/repository/CommentLikeRepository.java index 28c33f5..00e475d 100644 --- a/src/main/java/org/wooriverygood/api/comment/repository/CommentLikeRepository.java +++ b/src/main/java/org/wooriverygood/api/comment/repository/CommentLikeRepository.java @@ -3,14 +3,15 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.wooriverygood.api.comment.domain.Comment; import org.wooriverygood.api.comment.domain.CommentLike; +import org.wooriverygood.api.member.domain.Member; import java.util.Optional; public interface CommentLikeRepository extends JpaRepository { - Optional findByCommentAndUsername(Comment comment, String username); + Optional findByCommentAndMember(Comment comment, Member member); - boolean existsByCommentAndUsername(Comment comment, String username); + boolean existsByCommentAndMember(Comment comment, Member member); void deleteAllByComment(Comment comment); } diff --git a/src/main/java/org/wooriverygood/api/course/domain/Course.java b/src/main/java/org/wooriverygood/api/course/domain/Course.java index efab51b..bda5b32 100644 --- a/src/main/java/org/wooriverygood/api/course/domain/Course.java +++ b/src/main/java/org/wooriverygood/api/course/domain/Course.java @@ -1,6 +1,7 @@ package org.wooriverygood.api.course.domain; import jakarta.persistence.*; +import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -8,8 +9,8 @@ @Entity @Getter -@NoArgsConstructor @Table(name = "courses") +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class Course { @Id @@ -36,6 +37,7 @@ public class Course { @ColumnDefault("0") private int reviewCount; + @Builder public Course(Long id, String name, String category, double credit, int isYouguan, String kaikeYuanxi, int reviewCount) { this.id = id; diff --git a/src/test/java/org/wooriverygood/api/comment/api/CommentApiTest.java b/src/test/java/org/wooriverygood/api/comment/api/CommentApiTest.java index 9a8179d..b364b5b 100644 --- a/src/test/java/org/wooriverygood/api/comment/api/CommentApiTest.java +++ b/src/test/java/org/wooriverygood/api/comment/api/CommentApiTest.java @@ -22,7 +22,6 @@ import static org.mockito.Mockito.*; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; - class CommentApiTest extends ApiTest { private List responses = new ArrayList<>(); @@ -43,7 +42,6 @@ void setUp() { responses.add(CommentResponse.builder() .commentId(i) .commentContent("content" + i) - .commentAuthor("user-"+(i % 5)) .postId(post.getId()) .commentLikeCount((int) i + 8) .commentTime(LocalDateTime.now()) @@ -51,6 +49,8 @@ void setUp() { .replies(new ArrayList<>()) .updated(i % 2 == 0) .reported(false) + .memberId(1L) + .isMine(true) .build()); } for (int i = 12; i <= 15; i++) { @@ -58,7 +58,8 @@ void setUp() { .add(ReplyResponse.builder() .replyId((long) i) .replyContent("reply content " + i) - .replyAuthor("user-" + (i % 2)) + .memberId(1L) + .isMine(true) .replyLikeCount(i - 6) .replyTime(LocalDateTime.now()) .liked(false) diff --git a/src/test/java/org/wooriverygood/api/comment/application/CommentCreateServiceTest.java b/src/test/java/org/wooriverygood/api/comment/application/CommentCreateServiceTest.java index 9aa3fcc..8826a3b 100644 --- a/src/test/java/org/wooriverygood/api/comment/application/CommentCreateServiceTest.java +++ b/src/test/java/org/wooriverygood/api/comment/application/CommentCreateServiceTest.java @@ -1,6 +1,5 @@ package org.wooriverygood.api.comment.application; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; @@ -11,16 +10,13 @@ import org.wooriverygood.api.comment.dto.NewReplyRequest; import org.wooriverygood.api.comment.repository.CommentRepository; import org.wooriverygood.api.member.repository.MemberRepository; -import org.wooriverygood.api.post.domain.Post; -import org.wooriverygood.api.post.domain.PostCategory; import org.wooriverygood.api.post.repository.PostRepository; -import org.wooriverygood.api.util.MockTest; -import java.util.ArrayList; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.verify; @@ -40,38 +36,19 @@ class CommentCreateServiceTest extends CommentServiceTest { @Mock private MemberRepository memberRepository; - private Comment comment; - - private Comment reply; - - - @BeforeEach - void setUp() { - comment = Comment.builder() - .post(post) - .build(); - reply = Comment.builder() - .parent(comment) - .build(); - } @Test @DisplayName("특정 게시글의 댓글을 작성한다.") void addComment() { - NewCommentRequest newCommentRequest = NewCommentRequest.builder() + NewCommentRequest request = NewCommentRequest.builder() .content("comment content") .build(); - - when(commentRepository.save(any(Comment.class))) - .thenReturn(Comment.builder() - .author(authInfo.getUsername()) - .content(newCommentRequest.getContent()) - .build()); - + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); when(postRepository.findById(anyLong())) .thenReturn(Optional.ofNullable(post)); - commentCreateService.addComment(authInfo, 1L, newCommentRequest); + commentCreateService.addComment(authInfo, post.getId(), request); verify(commentRepository).save(any(Comment.class)); } @@ -82,15 +59,18 @@ void addReply() { NewReplyRequest request = NewReplyRequest.builder() .content("reply content") .build(); - when(commentRepository.findById(anyLong())) - .thenReturn(Optional.ofNullable(comment)); + .thenReturn(Optional.ofNullable(commentWithoutReply)); + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); - commentCreateService.addReply(1L, request, authInfo); - Comment reply = comment.getReplies().get(0); + commentCreateService.addReply(commentWithoutReply.getId(), request, authInfo); - assertThat(reply.getContent()).isEqualTo(request.getContent()); - assertThat(reply.getParent()).isEqualTo(comment); + assertAll( + () -> assertThat(commentWithoutReply.getReplies().size()).isEqualTo(1), + () -> assertThat(commentWithoutReply.getReplies().get(0).getContent()).isEqualTo(request.getContent()), + () -> assertThat(commentWithoutReply.getReplies().get(0).getParent()).isEqualTo(commentWithoutReply) + ); } @Test @@ -99,11 +79,10 @@ void addReply_exception_depth() { NewReplyRequest request = NewReplyRequest.builder() .content("reply content") .build(); - when(commentRepository.findById(anyLong())) .thenReturn(Optional.ofNullable(reply)); - assertThatThrownBy(() -> commentCreateService.addReply(1L, request, authInfo)) + assertThatThrownBy(() -> commentCreateService.addReply(reply.getId(), request, authInfo)) .isInstanceOf(ReplyDepthException.class); } diff --git a/src/test/java/org/wooriverygood/api/comment/application/CommentDeleteServiceTest.java b/src/test/java/org/wooriverygood/api/comment/application/CommentDeleteServiceTest.java index 6ef297a..e7075a1 100644 --- a/src/test/java/org/wooriverygood/api/comment/application/CommentDeleteServiceTest.java +++ b/src/test/java/org/wooriverygood/api/comment/application/CommentDeleteServiceTest.java @@ -4,20 +4,21 @@ import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.wooriverygood.api.comment.domain.Comment; import org.wooriverygood.api.comment.repository.CommentLikeRepository; import org.wooriverygood.api.comment.repository.CommentRepository; import org.wooriverygood.api.global.error.exception.AuthorizationException; -import org.wooriverygood.api.util.MockTest; +import org.wooriverygood.api.member.domain.Member; +import org.wooriverygood.api.member.repository.MemberRepository; import java.util.Optional; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.*; -class CommentDeleteServiceTest extends MockTest { +class CommentDeleteServiceTest extends CommentServiceTest { @InjectMocks private CommentDeleteService commentDeleteService; @@ -29,54 +30,49 @@ class CommentDeleteServiceTest extends MockTest { private CommentLikeRepository commentLikeRepository; @Mock - private Comment comment; - - @Mock - private Comment reply; + private MemberRepository memberRepository; @Test @DisplayName("권한이 있는 댓글을 삭제한다.") void deleteComment() { + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); when(commentRepository.findById(anyLong())) .thenReturn(Optional.ofNullable(comment)); - when(comment.isParent()) - .thenReturn(true); - when(comment.hasNoReply()) - .thenReturn(true); + comment.deleteReply(reply); commentDeleteService.deleteComment(comment.getId(), authInfo); - verify(comment).validateAuthor(authInfo.getUsername()); - verify(commentLikeRepository).deleteAllByComment(comment); - verify(commentRepository).delete(comment); + assertAll( + () -> verify(commentLikeRepository).deleteAllByComment(comment), + () -> verify(commentRepository).delete(comment) + ); } @Test @DisplayName("권한이 있는 대댓글을 삭제한다.") void deleteReply() { + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); when(commentRepository.findById(anyLong())) .thenReturn(Optional.ofNullable(reply)); - when(reply.isParent()) - .thenReturn(false); - when(reply.getParent()) - .thenReturn(comment); + commentDeleteService.deleteComment(reply.getId(), authInfo); - verify(reply).validateAuthor(authInfo.getUsername()); - verify(commentLikeRepository).deleteAllByComment(reply); - verify(comment).deleteReply(reply); - verify(commentRepository).delete(reply); + assertAll( + () -> verify(commentLikeRepository).deleteAllByComment(reply), + () -> verify(commentRepository).delete(reply) + ); } @Test @DisplayName("권한이 없는 대댓글을 삭제할 수 없다.") void deleteReply_exception_noAuth() { + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.of(new Member(5L, "username"))); when(commentRepository.findById(anyLong())) .thenReturn(Optional.ofNullable(reply)); - doThrow(new AuthorizationException()) - .when(reply) - .validateAuthor(anyString()); assertThatThrownBy(() -> commentDeleteService.deleteComment(reply.getId(), authInfo)) .isInstanceOf(AuthorizationException.class); @@ -85,45 +81,43 @@ void deleteReply_exception_noAuth() { @Test @DisplayName("부모 댓글을 삭제해도 대댓글은 남아있다.") void deleteComment_keepChildren() { + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); when(commentRepository.findById(anyLong())) .thenReturn(Optional.ofNullable(comment)); - when(comment.isParent()) - .thenReturn(true); - when(comment.hasNoReply()) - .thenReturn(false); commentDeleteService.deleteComment(comment.getId(), authInfo); - verify(comment).willBeDeleted(); + assertAll( + () -> assertThat(comment.isSoftRemoved()).isEqualTo(true), + () -> assertThat(comment.canDelete()).isEqualTo(false) + ); } @Test @DisplayName("특정 대댓글 삭제 후, 삭제 예정으로 처리되고 대댓글이 없는 부모 댓글을 삭제한다.") void deletePrentAndReply() { + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); when(commentRepository.findById(anyLong())) .thenReturn(Optional.ofNullable(reply)); - when(reply.isParent()) - .thenReturn(false); - when(reply.getParent()) - .thenReturn(comment); - when(comment.canDelete()) - .thenReturn(true); + comment.willBeDeleted(); commentDeleteService.deleteComment(reply.getId(), authInfo); - verify(comment).deleteReply(reply); - verify(commentRepository).delete(reply); - verify(commentRepository).delete(comment); + assertAll( + () -> verify(commentRepository).delete(reply), + () -> verify(commentRepository).delete(comment) + ); } @Test @DisplayName("권한이 없는 댓글은 삭제할 수 없다") void deleteComment_exception_noAuth() { + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.of(new Member(5L, "username"))); when(commentRepository.findById(anyLong())) .thenReturn(Optional.ofNullable(comment)); - doThrow(new AuthorizationException()) - .when(comment) - .validateAuthor(anyString()); assertThatThrownBy(() -> commentDeleteService.deleteComment(comment.getId(), authInfo)) .isInstanceOf(AuthorizationException.class); diff --git a/src/test/java/org/wooriverygood/api/comment/application/CommentFindServiceTest.java b/src/test/java/org/wooriverygood/api/comment/application/CommentFindServiceTest.java index 6db7fe5..2fe1f11 100644 --- a/src/test/java/org/wooriverygood/api/comment/application/CommentFindServiceTest.java +++ b/src/test/java/org/wooriverygood/api/comment/application/CommentFindServiceTest.java @@ -9,12 +9,12 @@ import org.wooriverygood.api.comment.dto.CommentsResponse; import org.wooriverygood.api.comment.repository.CommentLikeRepository; import org.wooriverygood.api.comment.repository.CommentRepository; -import org.wooriverygood.api.post.domain.Post; -import org.wooriverygood.api.post.domain.PostCategory; -import org.wooriverygood.api.util.MockTest; +import org.wooriverygood.api.member.domain.Member; +import org.wooriverygood.api.member.repository.MemberRepository; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.*; @@ -31,18 +31,22 @@ class CommentFindServiceTest extends CommentServiceTest { @Mock private CommentLikeRepository commentLikeRepository; + @Mock + private MemberRepository memberRepository; + private final int COMMENT_COUNT = 10; private List comments = new ArrayList<>(); + @BeforeEach void setUp() { - for (int i = 0; i < COMMENT_COUNT; i++) { + for (long i = 0; i < COMMENT_COUNT; i++) { Comment comment = Comment.builder() - .id((long) i) + .id(i) .content("comment" + i) - .author("author" + i) .post(post) + .member(member) .build(); comments.add(comment); } @@ -51,9 +55,11 @@ void setUp() { @Test @DisplayName("유효한 id를 통해 특정 게시글의 댓글들을 불러온다.") void findAllCommentsByPostId() { + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); when(commentRepository.findAllByPostId(anyLong())) .thenReturn(comments); - when(commentLikeRepository.existsByCommentAndUsername(any(Comment.class), anyString())) + when(commentLikeRepository.existsByCommentAndMember(any(Comment.class), any(Member.class))) .thenReturn(true); CommentsResponse response = commentFindService.findAllCommentsByPostId(2L, authInfo); @@ -68,10 +74,12 @@ void findAllCommentsByPostId_softRemoved() { comments.add(Comment.builder() .id(1L) .content("Content") - .author("Author") .post(post) + .member(member) .softRemoved(true) .build()); + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); when(commentRepository.findAllByPostId(anyLong())) .thenReturn(comments); @@ -83,20 +91,10 @@ void findAllCommentsByPostId_softRemoved() { @Test @DisplayName("대댓글만 불러올 수 없다.") void findAllCommentsByPostId_cannot_only_reply() { - Comment comment = Comment.builder() - .id(1L) - .content("Parent") - .author("author1") - .post(post) - .build(); comments = new ArrayList<>(); - comments.add(Comment.builder() - .id(2L) - .content("Child") - .author("Author2") - .post(post) - .parent(comment) - .build()); + comments.add(reply); + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); when(commentRepository.findAllByPostId(anyLong())) .thenReturn(comments); @@ -109,22 +107,10 @@ void findAllCommentsByPostId_cannot_only_reply() { @DisplayName("댓글과 대댓글들을 모두 불러온다.") void findAllCommentsByPostId_withReplies() { comments = new ArrayList<>(); - Comment comment = Comment.builder() - .id(1L) - .content("Parent") - .author("author1") - .post(post) - .build(); comments.add(comment); - Comment reply = Comment.builder() - .id(2L) - .content("Child") - .author("Author2") - .post(post) - .parent(comment) - .build(); comments.add(reply); - comment.addReply(reply); + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); when(commentRepository.findAllByPostId(anyLong())) .thenReturn(comments); diff --git a/src/test/java/org/wooriverygood/api/comment/application/CommentLikeToggleServiceTest.java b/src/test/java/org/wooriverygood/api/comment/application/CommentLikeToggleServiceTest.java index 6d6c1be..da1da3f 100644 --- a/src/test/java/org/wooriverygood/api/comment/application/CommentLikeToggleServiceTest.java +++ b/src/test/java/org/wooriverygood/api/comment/application/CommentLikeToggleServiceTest.java @@ -1,7 +1,5 @@ package org.wooriverygood.api.comment.application; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; @@ -11,14 +9,13 @@ import org.wooriverygood.api.comment.dto.*; import org.wooriverygood.api.comment.repository.CommentLikeRepository; import org.wooriverygood.api.comment.repository.CommentRepository; -import org.wooriverygood.api.post.domain.Post; -import org.wooriverygood.api.post.domain.PostCategory; -import org.wooriverygood.api.util.MockTest; +import org.wooriverygood.api.member.domain.Member; +import org.wooriverygood.api.member.repository.MemberRepository; -import java.util.ArrayList; -import java.util.List; import java.util.Optional; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.when; @@ -33,72 +30,49 @@ class CommentLikeToggleServiceTest extends CommentServiceTest { @Mock private CommentLikeRepository commentLikeRepository; - private final int COMMENT_COUNT = 10; - - List comments = new ArrayList<>(); - - private Comment comment = Comment.builder() - .id(2L) - .post(post) - .content("comment content") - .author(authInfo.getUsername()) - .commentLikes(new ArrayList<>()) - .build(); - - private Comment reply = Comment.builder() - .id(3L) - .post(post) - .content("reply content") - .author(authInfo.getUsername()) - .commentLikes(new ArrayList<>()) - .parent(comment) - .build(); - - - @BeforeEach - void setUpPosts() { - comment.getReplies().add(reply); - - for (int i = 0; i < COMMENT_COUNT; i++) { - Comment comment = Comment.builder() - .id((long) i) - .content("comment" + i) - .author("author" + i) - .post(post) - .build(); - comments.add(comment); - } - } + @Mock + private MemberRepository memberRepository; + @Test @DisplayName("특정 댓글의 좋아요를 1 올린다.") void likeComment_up() { + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); when(commentRepository.findById(anyLong())) .thenReturn(Optional.ofNullable(comment)); + when(commentLikeRepository.findByCommentAndMember(any(Comment.class), any(Member.class))) + .thenReturn(Optional.empty()); CommentLikeResponse response = commentLikeToggleService.likeComment(comment.getId(), authInfo); - Assertions.assertThat(response.getLikeCount()).isEqualTo(comment.getLikeCount() + 1); - Assertions.assertThat(response.isLiked()).isEqualTo(true); + assertAll( + () -> assertThat(response.getLikeCount()).isEqualTo(comment.getLikeCount() + 1), + () -> assertThat(response.isLiked()).isEqualTo(true) + ); } @Test @DisplayName("특정 댓글의 좋아요를 1 내린다.") void likeComment_down() { CommentLike commentLike = CommentLike.builder() - .id(2L) + .id(1L) .comment(comment) - .username(authInfo.getUsername()) + .member(member) .build(); + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); when(commentRepository.findById(anyLong())) .thenReturn(Optional.ofNullable(comment)); - when(commentLikeRepository.findByCommentAndUsername(any(Comment.class), anyString())) + when(commentLikeRepository.findByCommentAndMember(any(Comment.class), any(Member.class))) .thenReturn(Optional.ofNullable(commentLike)); CommentLikeResponse response = commentLikeToggleService.likeComment(comment.getId(), authInfo); - Assertions.assertThat(response.getLikeCount()).isEqualTo(comment.getLikeCount() - 1); - Assertions.assertThat(response.isLiked()).isEqualTo(false); + assertAll( + () -> assertThat(response.getLikeCount()).isEqualTo(comment.getLikeCount() - 1), + () -> assertThat(response.isLiked()).isEqualTo(false) + ); } } \ No newline at end of file diff --git a/src/test/java/org/wooriverygood/api/comment/application/CommentServiceTest.java b/src/test/java/org/wooriverygood/api/comment/application/CommentServiceTest.java index 7a6e38b..c611afd 100644 --- a/src/test/java/org/wooriverygood/api/comment/application/CommentServiceTest.java +++ b/src/test/java/org/wooriverygood/api/comment/application/CommentServiceTest.java @@ -1,6 +1,7 @@ package org.wooriverygood.api.comment.application; import org.junit.jupiter.api.BeforeEach; +import org.wooriverygood.api.comment.domain.Comment; import org.wooriverygood.api.post.domain.Post; import org.wooriverygood.api.post.domain.PostCategory; import org.wooriverygood.api.util.MockTest; @@ -11,6 +12,13 @@ public class CommentServiceTest extends MockTest { protected Post post; + protected Comment comment; + + protected Comment commentWithoutReply; + + protected Comment reply; + + @BeforeEach void dateSetUp() { post = Post.builder() @@ -22,6 +30,27 @@ void dateSetUp() { .comments(new ArrayList<>()) .postLikes(new ArrayList<>()) .build(); + comment = Comment.builder() + .id(1L) + .content("content") + .post(post) + .member(member) + .commentLikes(new ArrayList<>()) + .build(); + reply = Comment.builder() + .id(2L) + .content("content") + .post(post) + .parent(comment) + .member(member) + .build(); + comment.addReply(reply); + commentWithoutReply = Comment.builder() + .id(3L) + .content("content") + .post(post) + .member(member) + .build(); } } diff --git a/src/test/java/org/wooriverygood/api/comment/application/CommentUpdateServiceTest.java b/src/test/java/org/wooriverygood/api/comment/application/CommentUpdateServiceTest.java index da68f53..3baea56 100644 --- a/src/test/java/org/wooriverygood/api/comment/application/CommentUpdateServiceTest.java +++ b/src/test/java/org/wooriverygood/api/comment/application/CommentUpdateServiceTest.java @@ -1,24 +1,20 @@ package org.wooriverygood.api.comment.application; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.wooriverygood.api.comment.domain.Comment; import org.wooriverygood.api.comment.dto.CommentUpdateRequest; import org.wooriverygood.api.comment.repository.CommentRepository; -import org.wooriverygood.api.global.auth.AuthInfo; import org.wooriverygood.api.global.error.exception.AuthorizationException; -import org.wooriverygood.api.post.domain.Post; -import org.wooriverygood.api.post.domain.PostCategory; -import org.wooriverygood.api.util.MockTest; +import org.wooriverygood.api.member.domain.Member; +import org.wooriverygood.api.member.repository.MemberRepository; -import java.util.ArrayList; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.when; @@ -30,19 +26,9 @@ class CommentUpdateServiceTest extends CommentServiceTest { @Mock private CommentRepository commentRepository; - private Comment comment; - + @Mock + private MemberRepository memberRepository; - @BeforeEach - void setUp() { - comment = Comment.builder() - .id(2L) - .post(post) - .content("comment content") - .author(authInfo.getUsername()) - .commentLikes(new ArrayList<>()) - .build(); - } @Test @DisplayName("권한이 있는 댓글을 수정한다.") @@ -50,14 +36,17 @@ void updateComment() { CommentUpdateRequest request = CommentUpdateRequest.builder() .content("new comment content") .build(); - + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); when(commentRepository.findById(anyLong())) .thenReturn(Optional.ofNullable(comment)); commentUpdateService.updateComment(comment.getId(), request, authInfo); - assertThat(comment.isUpdated()).isEqualTo(true); - assertThat(comment.getContent()).isEqualTo(request.getContent()); + assertAll( + () -> assertThat(comment.isUpdated()).isEqualTo(true), + () -> assertThat(comment.getContent()).isEqualTo(request.getContent()) + ); } @Test @@ -66,15 +55,12 @@ void updateComment_exception_noAuth() { CommentUpdateRequest request = CommentUpdateRequest.builder() .content("new comment content") .build(); - AuthInfo noAuthInfo = AuthInfo.builder() - .sub("no") - .username("no") - .build(); - + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(new Member(5L, "username"))); when(commentRepository.findById(anyLong())) .thenReturn(Optional.ofNullable(comment)); - assertThatThrownBy(() -> commentUpdateService.updateComment(comment.getId(), request, noAuthInfo)) + assertThatThrownBy(() -> commentUpdateService.updateComment(comment.getId(), request, authInfo)) .isInstanceOf(AuthorizationException.class); } diff --git a/src/test/java/org/wooriverygood/api/post/api/PostApiTest.java b/src/test/java/org/wooriverygood/api/post/api/PostApiTest.java index 82a0f4c..a86351c 100644 --- a/src/test/java/org/wooriverygood/api/post/api/PostApiTest.java +++ b/src/test/java/org/wooriverygood/api/post/api/PostApiTest.java @@ -56,6 +56,8 @@ void findPostsByCategory() { .liked(i % 6 == 0) .updated(i % 9 == 0) .reported(false) + .memberId(1L) + .isMine(false) .build()); when(postFindService.findPosts(any(AuthInfo.class), any(Pageable.class), anyString())) @@ -89,6 +91,8 @@ void findPost() { .liked(false) .updated(false) .reported(false) + .memberId(1L) + .isMine(false) .build()); restDocs diff --git a/src/test/java/org/wooriverygood/api/report/application/CommentReportServiceTest.java b/src/test/java/org/wooriverygood/api/report/application/CommentReportServiceTest.java index ce54bd3..ba1d205 100644 --- a/src/test/java/org/wooriverygood/api/report/application/CommentReportServiceTest.java +++ b/src/test/java/org/wooriverygood/api/report/application/CommentReportServiceTest.java @@ -58,14 +58,6 @@ void setUp() { .comments(new ArrayList<>()) .postLikes(new ArrayList<>()) .build(); - comment = Comment.builder() - .id(1L) - .content("Parent") - .author("author1") - .post(post) - .member(member) - .reports(new ArrayList<>()) - .build(); } @Test diff --git a/src/test/java/org/wooriverygood/api/util/ResponseFixture.java b/src/test/java/org/wooriverygood/api/util/ResponseFixture.java index 0925a3d..af0f2f8 100644 --- a/src/test/java/org/wooriverygood/api/util/ResponseFixture.java +++ b/src/test/java/org/wooriverygood/api/util/ResponseFixture.java @@ -30,6 +30,8 @@ public static PostResponse postResponse(long id, String category, AuthInfo authI .liked(id % 6 == 0) .updated(id % 9 == 0) .reported(false) + .memberId(1L) + .isMine(false) .build(); } @@ -52,6 +54,8 @@ public static PostResponse reportedPostResponse(long id, String category, AuthIn .liked(id % 6 == 0) .updated(id % 9 == 0) .reported(true) + .memberId(1L) + .isMine(false) .build(); } From 0be51b588adf6d2e9cbe9ea200927ce22bd9fb75 Mon Sep 17 00:00:00 2001 From: Park Geonho Date: Mon, 29 Apr 2024 00:06:51 +0800 Subject: [PATCH 19/21] wip --- .../application/PostLikeToggleService.java | 6 ++-- .../wooriverygood/api/post/domain/Post.java | 2 +- .../api/post/domain/PostLike.java | 11 ++++--- .../api/report/domain/CommentReport.java | 3 +- .../api/report/domain/PostReport.java | 1 + .../application/ReviewCreateService.java | 5 ++- .../application/ReviewLikeToggleService.java | 6 ++-- .../api/review/domain/Review.java | 9 ++---- .../api/review/domain/ReviewLike.java | 18 ++++++----- .../application/CommentServiceTest.java | 1 + .../PostLikeToggleServiceTest.java | 2 +- .../api/post/application/PostServiceTest.java | 1 + .../application/CommentReportServiceTest.java | 5 ++- .../application/PostReportServiceTest.java | 31 ++++++++++--------- .../application/ReviewDeleteServiceTest.java | 23 ++++++++------ .../application/ReviewFindServiceTest.java | 1 - .../ReviewLikeToggleServiceTest.java | 5 +-- .../application/ReviewUpdateServiceTest.java | 19 +++++++----- .../org/wooriverygood/api/util/ApiTest.java | 4 +++ 19 files changed, 83 insertions(+), 70 deletions(-) diff --git a/src/main/java/org/wooriverygood/api/post/application/PostLikeToggleService.java b/src/main/java/org/wooriverygood/api/post/application/PostLikeToggleService.java index 01e61ee..29508ce 100644 --- a/src/main/java/org/wooriverygood/api/post/application/PostLikeToggleService.java +++ b/src/main/java/org/wooriverygood/api/post/application/PostLikeToggleService.java @@ -37,7 +37,7 @@ public PostLikeResponse togglePostLike(long postId, AuthInfo authInfo) { Optional postLike = postLikeRepository.findByPostAndMember(post, member); if (postLike.isEmpty()) { - addPostLike(post, authInfo.getUsername()); + addPostLike(post, member); return createPostLikeResponse(post, true); } @@ -45,10 +45,10 @@ public PostLikeResponse togglePostLike(long postId, AuthInfo authInfo) { return createPostLikeResponse(post, false); } - private void addPostLike(Post post, String username) { + private void addPostLike(Post post, Member member) { PostLike newPostLike = PostLike.builder() .post(post) - .username(username) + .member(member) .build(); post.addPostLike(newPostLike); diff --git a/src/main/java/org/wooriverygood/api/post/domain/Post.java b/src/main/java/org/wooriverygood/api/post/domain/Post.java index a043f3c..ae1b951 100644 --- a/src/main/java/org/wooriverygood/api/post/domain/Post.java +++ b/src/main/java/org/wooriverygood/api/post/domain/Post.java @@ -15,8 +15,8 @@ import java.util.List; @Entity -@Table(name = "posts") @Getter +@Table(name = "posts") @EntityListeners(AuditingEntityListener.class) @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Post { diff --git a/src/main/java/org/wooriverygood/api/post/domain/PostLike.java b/src/main/java/org/wooriverygood/api/post/domain/PostLike.java index d1c9131..fd74461 100644 --- a/src/main/java/org/wooriverygood/api/post/domain/PostLike.java +++ b/src/main/java/org/wooriverygood/api/post/domain/PostLike.java @@ -2,10 +2,11 @@ import jakarta.persistence.*; import lombok.*; +import org.wooriverygood.api.member.domain.Member; @Entity -@Table(name = "postLikes") @Getter +@Table(name = "post_likes") @NoArgsConstructor(access = AccessLevel.PROTECTED) public class PostLike { @@ -17,15 +18,15 @@ public class PostLike { @JoinColumn(name = "post_id", referencedColumnName = "post_id") private Post post; - @Column - private String username; + @ManyToOne(fetch = FetchType.LAZY) + private Member member; @Builder - public PostLike(Long id, Post post, String username) { + public PostLike(Long id, Post post, Member member) { this.id = id; this.post = post; - this.username = username; + this.member = member; } public void delete() { diff --git a/src/main/java/org/wooriverygood/api/report/domain/CommentReport.java b/src/main/java/org/wooriverygood/api/report/domain/CommentReport.java index 483362c..917a59f 100644 --- a/src/main/java/org/wooriverygood/api/report/domain/CommentReport.java +++ b/src/main/java/org/wooriverygood/api/report/domain/CommentReport.java @@ -1,6 +1,7 @@ package org.wooriverygood.api.report.domain; import jakarta.persistence.*; +import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -10,7 +11,7 @@ @Entity @Getter @Table(name = "comment_reports") -@NoArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class CommentReport { @Id diff --git a/src/main/java/org/wooriverygood/api/report/domain/PostReport.java b/src/main/java/org/wooriverygood/api/report/domain/PostReport.java index 9c250cb..e34ae25 100644 --- a/src/main/java/org/wooriverygood/api/report/domain/PostReport.java +++ b/src/main/java/org/wooriverygood/api/report/domain/PostReport.java @@ -10,6 +10,7 @@ @Entity @Getter +@Table(name = "post_reports") @NoArgsConstructor(access = AccessLevel.PROTECTED) public class PostReport { diff --git a/src/main/java/org/wooriverygood/api/review/application/ReviewCreateService.java b/src/main/java/org/wooriverygood/api/review/application/ReviewCreateService.java index 4c10231..b059183 100644 --- a/src/main/java/org/wooriverygood/api/review/application/ReviewCreateService.java +++ b/src/main/java/org/wooriverygood/api/review/application/ReviewCreateService.java @@ -31,14 +31,14 @@ public void addReview(AuthInfo authInfo, Long courseId, NewReviewRequest request .orElseThrow(CourseNotFoundException::new); Member member = memberRepository.findById(authInfo.getMemberId()) .orElseThrow(MemberNotFoundException::new); - Review review = createReview(course, authInfo, request, member); + Review review = createReview(course, request, member); member.addReview(review); reviewRepository.save(review); courseRepository.increaseReviewCount(review.getCourse().getId()); } - private Review createReview(Course course, AuthInfo authInfo, NewReviewRequest request, Member member) { + private Review createReview(Course course, NewReviewRequest request, Member member) { return Review.builder() .reviewTitle(request.getReviewTitle()) .course(course) @@ -47,7 +47,6 @@ private Review createReview(Course course, AuthInfo authInfo, NewReviewRequest r .takenSemyr(request.getTakenSemyr()) .reviewContent(request.getReviewContent()) .grade(request.getGrade()) - .authorEmail(authInfo.getUsername()) .build(); } diff --git a/src/main/java/org/wooriverygood/api/review/application/ReviewLikeToggleService.java b/src/main/java/org/wooriverygood/api/review/application/ReviewLikeToggleService.java index 6159b42..44925c0 100644 --- a/src/main/java/org/wooriverygood/api/review/application/ReviewLikeToggleService.java +++ b/src/main/java/org/wooriverygood/api/review/application/ReviewLikeToggleService.java @@ -37,7 +37,7 @@ public ReviewLikeResponse toggleReviewLike(Long reviewId, AuthInfo authInfo) { Optional reviewLike = reviewLikeRepository.findByReviewAndMember(review, member); if (reviewLike.isEmpty()) { - addReviewLike(review, authInfo.getUsername()); + addReviewLike(review, member); return createReviewLikeResponse(review, true); } @@ -46,10 +46,10 @@ public ReviewLikeResponse toggleReviewLike(Long reviewId, AuthInfo authInfo) { } - private void addReviewLike(Review review, String username) { + private void addReviewLike(Review review, Member member) { ReviewLike reviewLike = ReviewLike.builder() .review(review) - .username(username) + .member(member) .build(); review.addReviewLike(reviewLike); diff --git a/src/main/java/org/wooriverygood/api/review/domain/Review.java b/src/main/java/org/wooriverygood/api/review/domain/Review.java index 3862efb..2e339a0 100644 --- a/src/main/java/org/wooriverygood/api/review/domain/Review.java +++ b/src/main/java/org/wooriverygood/api/review/domain/Review.java @@ -1,6 +1,7 @@ package org.wooriverygood.api.review.domain; import jakarta.persistence.*; +import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -16,9 +17,9 @@ @Entity @Getter -@NoArgsConstructor @Table(name = "reviews") @EntityListeners(AuditingEntityListener.class) +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class Review { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -44,9 +45,6 @@ public class Review { @Column(name = "grade", length = 45, nullable = false) private String grade; - @Column(name = "author_email", length = 300) - private String authorEmail; - @ManyToOne(fetch = FetchType.LAZY) private Member member; @@ -66,7 +64,7 @@ public class Review { @Builder public Review(Long id, Course course, String reviewContent, String reviewTitle, String instructorName, - String takenSemyr, String grade, String authorEmail, LocalDateTime createdAt, Member member, + String takenSemyr, String grade, LocalDateTime createdAt, Member member, List reviewLikes, boolean updated) { this.id = id; this.course = course; @@ -75,7 +73,6 @@ public Review(Long id, Course course, String reviewContent, String reviewTitle, this.instructorName = instructorName; this.takenSemyr = takenSemyr; this.grade = grade; - this.authorEmail = authorEmail; this.createdAt = createdAt; this.reviewLikes = reviewLikes; this.updated = updated; diff --git a/src/main/java/org/wooriverygood/api/review/domain/ReviewLike.java b/src/main/java/org/wooriverygood/api/review/domain/ReviewLike.java index 96e0960..bbf5398 100644 --- a/src/main/java/org/wooriverygood/api/review/domain/ReviewLike.java +++ b/src/main/java/org/wooriverygood/api/review/domain/ReviewLike.java @@ -1,30 +1,34 @@ package org.wooriverygood.api.review.domain; import jakarta.persistence.*; +import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.wooriverygood.api.member.domain.Member; @Entity -@Table(name = "reviewLikes") @Getter -@NoArgsConstructor +@Table(name = "review_likes") +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class ReviewLike { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "review_id", referencedColumnName = "review_id") private Review review; - @Column - private String username; + @ManyToOne(fetch = FetchType.LAZY) + private Member member; + + @Builder - public ReviewLike(Long id, Review review, String username) { + public ReviewLike(Long id, Review review, Member member) { this.id = id; this.review = review; - this.username = username; + this.member = member; } public void delete() { review = null; } diff --git a/src/test/java/org/wooriverygood/api/comment/application/CommentServiceTest.java b/src/test/java/org/wooriverygood/api/comment/application/CommentServiceTest.java index c611afd..b436d21 100644 --- a/src/test/java/org/wooriverygood/api/comment/application/CommentServiceTest.java +++ b/src/test/java/org/wooriverygood/api/comment/application/CommentServiceTest.java @@ -36,6 +36,7 @@ void dateSetUp() { .post(post) .member(member) .commentLikes(new ArrayList<>()) + .reports(new ArrayList<>()) .build(); reply = Comment.builder() .id(2L) diff --git a/src/test/java/org/wooriverygood/api/post/application/PostLikeToggleServiceTest.java b/src/test/java/org/wooriverygood/api/post/application/PostLikeToggleServiceTest.java index 749cc90..c4722e6 100644 --- a/src/test/java/org/wooriverygood/api/post/application/PostLikeToggleServiceTest.java +++ b/src/test/java/org/wooriverygood/api/post/application/PostLikeToggleServiceTest.java @@ -58,7 +58,7 @@ void likePost_down() { PostLike postLike = PostLike.builder() .id(1L) .post(post) - .username(authInfo.getUsername()) + .member(member) .build(); when(memberRepository.findById(anyLong())) .thenReturn(Optional.ofNullable(member)); diff --git a/src/test/java/org/wooriverygood/api/post/application/PostServiceTest.java b/src/test/java/org/wooriverygood/api/post/application/PostServiceTest.java index 661ed89..6b944df 100644 --- a/src/test/java/org/wooriverygood/api/post/application/PostServiceTest.java +++ b/src/test/java/org/wooriverygood/api/post/application/PostServiceTest.java @@ -21,6 +21,7 @@ void dateSetUp() { .member(member) .comments(new ArrayList<>()) .postLikes(new ArrayList<>()) + .reports(new ArrayList<>()) .build(); } diff --git a/src/test/java/org/wooriverygood/api/report/application/CommentReportServiceTest.java b/src/test/java/org/wooriverygood/api/report/application/CommentReportServiceTest.java index ba1d205..55961e3 100644 --- a/src/test/java/org/wooriverygood/api/report/application/CommentReportServiceTest.java +++ b/src/test/java/org/wooriverygood/api/report/application/CommentReportServiceTest.java @@ -5,6 +5,7 @@ import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.wooriverygood.api.comment.application.CommentServiceTest; import org.wooriverygood.api.comment.exception.CommentNotFoundException; import org.wooriverygood.api.member.repository.MemberRepository; import org.wooriverygood.api.post.domain.Post; @@ -28,7 +29,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -class CommentReportServiceTest extends MockTest { +class CommentReportServiceTest extends CommentServiceTest { @InjectMocks private CommentReportService commentReportService; @@ -44,8 +45,6 @@ class CommentReportServiceTest extends MockTest { private Post post; - private Comment comment; - @BeforeEach void setUp() { diff --git a/src/test/java/org/wooriverygood/api/report/application/PostReportServiceTest.java b/src/test/java/org/wooriverygood/api/report/application/PostReportServiceTest.java index 8223f08..f5fe642 100644 --- a/src/test/java/org/wooriverygood/api/report/application/PostReportServiceTest.java +++ b/src/test/java/org/wooriverygood/api/report/application/PostReportServiceTest.java @@ -4,21 +4,20 @@ import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.wooriverygood.api.global.auth.AuthInfo; import org.wooriverygood.api.member.domain.Member; +import org.wooriverygood.api.member.repository.MemberRepository; import org.wooriverygood.api.post.application.PostServiceTest; -import org.wooriverygood.api.post.domain.Post; import org.wooriverygood.api.post.exception.PostNotFoundException; import org.wooriverygood.api.post.repository.PostRepository; import org.wooriverygood.api.report.domain.PostReport; import org.wooriverygood.api.report.dto.ReportRequest; import org.wooriverygood.api.report.exception.DuplicatedPostReportException; import org.wooriverygood.api.report.repository.PostReportRepository; -import org.wooriverygood.api.util.MockTest; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -35,35 +34,36 @@ class PostReportServiceTest extends PostServiceTest { private PostReportRepository postReportRepository; @Mock - private Post post; + private MemberRepository memberRepository; @Test @DisplayName("유효한 id를 통해 특정 게시글을 신고한다.") void reportPost() { ReportRequest request = new ReportRequest("report message"); - when(post.hasReportByMember(any(Member.class))) - .thenReturn(false); + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); when(postRepository.findById(anyLong())) .thenReturn(Optional.ofNullable(post)); postReportService.reportPost(1L, request, authInfo); - verify(post).hasReportByMember(member); - verify(post).addReport(any(PostReport.class)); - verify(postRepository).increaseReportCount(anyLong()); - verify(postReportRepository).save(any(PostReport.class)); + assertAll( + () -> verify(postRepository).increaseReportCount(post.getId()), + () -> verify(postReportRepository).save(any(PostReport.class)) + ); } @Test @DisplayName("신고하려는 게시글 id가 유효하지 않으면 예외를 발생한다.") void reportPost_exception_invalidId() { ReportRequest request = new ReportRequest("report message"); - + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); when(postRepository.findById(anyLong())) .thenThrow(new PostNotFoundException()); - assertThatThrownBy(() -> postReportService.reportPost(1L, request, authInfo)) + assertThatThrownBy(() -> postReportService.reportPost(post.getId(), request, authInfo)) .isInstanceOf(PostNotFoundException.class); } @@ -71,12 +71,13 @@ void reportPost_exception_invalidId() { @DisplayName("동일한 게시글을 한 번 이상 신고하면 예외를 발생한다.") void reportPost_exception_duplicated() { ReportRequest request = new ReportRequest("report message"); - when(post.hasReportByMember(any(Member.class))) - .thenReturn(true); + when(memberRepository.findById(anyLong())) + .thenReturn(Optional.ofNullable(member)); when(postRepository.findById(anyLong())) .thenReturn(Optional.ofNullable(post)); + postReportService.reportPost(post.getId(), request, authInfo); - assertThatThrownBy(() -> postReportService.reportPost(1L, request, authInfo)) + assertThatThrownBy(() -> postReportService.reportPost(post.getId(), request, authInfo)) .isInstanceOf(DuplicatedPostReportException.class); } diff --git a/src/test/java/org/wooriverygood/api/review/application/ReviewDeleteServiceTest.java b/src/test/java/org/wooriverygood/api/review/application/ReviewDeleteServiceTest.java index fa83078..0099a3b 100644 --- a/src/test/java/org/wooriverygood/api/review/application/ReviewDeleteServiceTest.java +++ b/src/test/java/org/wooriverygood/api/review/application/ReviewDeleteServiceTest.java @@ -1,12 +1,12 @@ package org.wooriverygood.api.review.application; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; import org.wooriverygood.api.course.domain.Course; import org.wooriverygood.api.course.repository.CourseRepository; -import org.wooriverygood.api.global.auth.AuthInfo; import org.wooriverygood.api.global.error.exception.AuthorizationException; import org.wooriverygood.api.member.domain.Member; import org.wooriverygood.api.member.repository.MemberRepository; @@ -21,7 +21,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.*; class ReviewDeleteServiceTest extends MockTest { @@ -41,14 +40,20 @@ class ReviewDeleteServiceTest extends MockTest { @Mock private MemberRepository memberRepository; - private Course course = Course.builder() - .id(1L) - .build(); + private Course course; - private Review review = Review.builder() - .member(member) - .course(course) - .build(); + private Review review; + + @BeforeEach + void setUp() { + course = Course.builder() + .id(1L) + .build(); + review = Review.builder() + .member(member) + .course(course) + .build(); + } @Test @DisplayName("권한이 있는 리뷰를 삭제한다.") diff --git a/src/test/java/org/wooriverygood/api/review/application/ReviewFindServiceTest.java b/src/test/java/org/wooriverygood/api/review/application/ReviewFindServiceTest.java index 24d5dea..b3fa693 100644 --- a/src/test/java/org/wooriverygood/api/review/application/ReviewFindServiceTest.java +++ b/src/test/java/org/wooriverygood/api/review/application/ReviewFindServiceTest.java @@ -62,7 +62,6 @@ void setUp() { .instructorName("jiaoshou") .takenSemyr("22-23") .grade("60") - .authorEmail("author" + i) .reviewLikes(new ArrayList<>()) .updated(false) .build(); diff --git a/src/test/java/org/wooriverygood/api/review/application/ReviewLikeToggleServiceTest.java b/src/test/java/org/wooriverygood/api/review/application/ReviewLikeToggleServiceTest.java index f4896cf..c9f114e 100644 --- a/src/test/java/org/wooriverygood/api/review/application/ReviewLikeToggleServiceTest.java +++ b/src/test/java/org/wooriverygood/api/review/application/ReviewLikeToggleServiceTest.java @@ -2,10 +2,8 @@ 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.wooriverygood.api.member.domain.Member; import org.wooriverygood.api.member.repository.MemberRepository; import org.wooriverygood.api.review.domain.Review; @@ -13,7 +11,6 @@ import org.wooriverygood.api.review.dto.*; import org.wooriverygood.api.review.repository.ReviewLikeRepository; import org.wooriverygood.api.review.repository.ReviewRepository; -import org.wooriverygood.api.global.auth.AuthInfo; import org.wooriverygood.api.util.MockTest; import java.time.LocalDateTime; @@ -75,7 +72,7 @@ void likeReview_down() { ReviewLike reviewLike = ReviewLike.builder() .id(3L) .review(review) - .username(authInfo.getUsername()) + .member(member) .build(); when(reviewRepository.findById(anyLong())) diff --git a/src/test/java/org/wooriverygood/api/review/application/ReviewUpdateServiceTest.java b/src/test/java/org/wooriverygood/api/review/application/ReviewUpdateServiceTest.java index 6fcb895..5944d58 100644 --- a/src/test/java/org/wooriverygood/api/review/application/ReviewUpdateServiceTest.java +++ b/src/test/java/org/wooriverygood/api/review/application/ReviewUpdateServiceTest.java @@ -1,10 +1,10 @@ package org.wooriverygood.api.review.application; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.wooriverygood.api.global.auth.AuthInfo; import org.wooriverygood.api.global.error.exception.AuthorizationException; import org.wooriverygood.api.member.domain.Member; import org.wooriverygood.api.member.repository.MemberRepository; @@ -18,7 +18,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; class ReviewUpdateServiceTest extends MockTest { @@ -32,10 +31,15 @@ class ReviewUpdateServiceTest extends MockTest { @Mock private MemberRepository memberRepository; - private Review review = Review.builder() - .id(1L) - .member(member) - .build(); + private Review review; + + @BeforeEach + void setUp() { + review = Review.builder() + .id(1L) + .member(member) + .build(); + } @Test @DisplayName("권한이 있는 리뷰를 수정한다.") @@ -47,13 +51,12 @@ void updateReview() { .takenSemyr("18-19") .grade("100") .build(); - when(reviewRepository.findById(anyLong())) .thenReturn(Optional.ofNullable(review)); when(memberRepository.findById(anyLong())) .thenReturn(Optional.ofNullable(member)); - reviewUpdateService.updateReview(1L, request, authInfo); + reviewUpdateService.updateReview(review.getId(), request, authInfo); assertAll( () -> assertThat(review.getReviewTitle()).isEqualTo(request.getReviewTitle()), diff --git a/src/test/java/org/wooriverygood/api/util/ApiTest.java b/src/test/java/org/wooriverygood/api/util/ApiTest.java index 519bf5e..1ea00cc 100644 --- a/src/test/java/org/wooriverygood/api/util/ApiTest.java +++ b/src/test/java/org/wooriverygood/api/util/ApiTest.java @@ -14,6 +14,7 @@ import org.wooriverygood.api.comment.api.CommentApi; import org.wooriverygood.api.comment.application.*; import org.wooriverygood.api.course.application.CourseFindService; +import org.wooriverygood.api.member.repository.MemberRepository; import org.wooriverygood.api.post.application.*; import org.wooriverygood.api.report.api.ReportApi; import org.wooriverygood.api.report.application.CommentReportService; @@ -46,6 +47,9 @@ public class ApiTest { protected MockMvcRequestSpecification restDocs; + @MockBean + protected MemberRepository memberRepository; + @MockBean protected CourseCreateService courseCreateService; From ec7ec2385410cae56a59c311dc1bd73f1fd4c92f Mon Sep 17 00:00:00 2001 From: Park Geonho Date: Mon, 29 Apr 2024 00:22:28 +0800 Subject: [PATCH 20/21] =?UTF-8?q?[fix]=20PostRepository=20=EC=BF=BC?= =?UTF-8?q?=EB=A6=AC=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wooriverygood/api/post/application/PostFindService.java | 2 +- .../org/wooriverygood/api/post/repository/PostRepository.java | 3 ++- .../api/post/application/PostFindServiceTest.java | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/wooriverygood/api/post/application/PostFindService.java b/src/main/java/org/wooriverygood/api/post/application/PostFindService.java index f2ca9f7..76e5d92 100644 --- a/src/main/java/org/wooriverygood/api/post/application/PostFindService.java +++ b/src/main/java/org/wooriverygood/api/post/application/PostFindService.java @@ -59,9 +59,9 @@ public PostDetailResponse findPostById(long postId, AuthInfo authInfo) { } public PostsResponse findMyPosts(AuthInfo authInfo, Pageable pageable) { - Page page = postRepository.findByAuthorOrderByIdDesc(authInfo.getUsername(), pageable); Member member = memberRepository.findById(authInfo.getMemberId()) .orElseThrow(MemberNotFoundException::new); + Page page = postRepository.findByMemberOrderByIdDesc(member, pageable); return convertToPostsResponse(member, page); } diff --git a/src/main/java/org/wooriverygood/api/post/repository/PostRepository.java b/src/main/java/org/wooriverygood/api/post/repository/PostRepository.java index 9b0282d..12aa857 100644 --- a/src/main/java/org/wooriverygood/api/post/repository/PostRepository.java +++ b/src/main/java/org/wooriverygood/api/post/repository/PostRepository.java @@ -6,6 +6,7 @@ import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.transaction.annotation.Transactional; +import org.wooriverygood.api.member.domain.Member; import org.wooriverygood.api.post.domain.Post; import org.wooriverygood.api.post.domain.PostCategory; @@ -16,7 +17,7 @@ public interface PostRepository extends JpaRepository { Page findAllByCategoryOrderByIdDesc(PostCategory category, Pageable pageable); - Page findByAuthorOrderByIdDesc(String author, Pageable pageable); + Page findByMemberOrderByIdDesc(Member member, Pageable pageable); @Transactional @Modifying(clearAutomatically = true) diff --git a/src/test/java/org/wooriverygood/api/post/application/PostFindServiceTest.java b/src/test/java/org/wooriverygood/api/post/application/PostFindServiceTest.java index 6037831..d557dcc 100644 --- a/src/test/java/org/wooriverygood/api/post/application/PostFindServiceTest.java +++ b/src/test/java/org/wooriverygood/api/post/application/PostFindServiceTest.java @@ -154,7 +154,7 @@ void findMyPosts() { int end = Math.min(start + pageable.getPageSize(), this.myPosts.size()); List posts = this.myPosts.subList(start, end); PageImpl page = new PageImpl<>(posts, pageable, this.myPosts.size()); - when(postRepository.findByAuthorOrderByIdDesc(any(String.class), any(PageRequest.class))) + when(postRepository.findByMemberOrderByIdDesc(any(Member.class), any(PageRequest.class))) .thenReturn(page); when(memberRepository.findById(anyLong())) .thenReturn(Optional.ofNullable(member)); From 2410c246c39f3e5bea6f4b3c88eabd95d76c3277 Mon Sep 17 00:00:00 2001 From: Park Geonho Date: Mon, 29 Apr 2024 00:57:09 +0800 Subject: [PATCH 21/21] =?UTF-8?q?[fix]=20CommentLike=20=ED=85=8C=EC=9D=B4?= =?UTF-8?q?=EB=B8=94=20=EC=9D=B4=EB=A6=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/wooriverygood/api/comment/domain/CommentLike.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/wooriverygood/api/comment/domain/CommentLike.java b/src/main/java/org/wooriverygood/api/comment/domain/CommentLike.java index 5b9a741..361b595 100644 --- a/src/main/java/org/wooriverygood/api/comment/domain/CommentLike.java +++ b/src/main/java/org/wooriverygood/api/comment/domain/CommentLike.java @@ -7,7 +7,7 @@ import org.wooriverygood.api.member.domain.Member; @Entity -@Table(name = "commentLikes") +@Table(name = "comment_likes") @Getter @NoArgsConstructor public class CommentLike {