From cb45021e2a2985ce28d57657b7dbb8ff10c928e2 Mon Sep 17 00:00:00 2001 From: luckhee Date: Fri, 26 Sep 2025 15:09:13 +0900 Subject: [PATCH 1/7] =?UTF-8?q?=EA=B2=8C=EC=8B=9C=EA=B8=80=20controller=20?= =?UTF-8?q?=ED=86=B5=ED=95=A9=20=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/PostCommentController.java | 2 + .../controller/InformationPostController.java | 3 +- .../controller/QuestionPostController.java | 137 ++++++++++++++++++ .../post/repository/PostRepositoryCustom.java | 2 +- .../post/repository/PostRepositoryImpl.java | 9 +- .../domain/post/post/service/PostService.java | 4 +- .../InformationPostControllerTest.java | 2 +- 7 files changed, 151 insertions(+), 8 deletions(-) create mode 100644 back/src/main/java/com/back/domain/post/post/controller/QuestionPostController.java diff --git a/back/src/main/java/com/back/domain/post/comment/controller/PostCommentController.java b/back/src/main/java/com/back/domain/post/comment/controller/PostCommentController.java index 2655cca6..ad497b88 100644 --- a/back/src/main/java/com/back/domain/post/comment/controller/PostCommentController.java +++ b/back/src/main/java/com/back/domain/post/comment/controller/PostCommentController.java @@ -9,6 +9,7 @@ import com.back.global.rq.Rq; import com.back.global.rsData.RsData; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import jakarta.validation.constraints.Positive; import lombok.RequiredArgsConstructor; @@ -20,6 +21,7 @@ @RestController @RequestMapping("/post/comment") + public class PostCommentController { @Autowired private Rq rq; diff --git a/back/src/main/java/com/back/domain/post/post/controller/InformationPostController.java b/back/src/main/java/com/back/domain/post/post/controller/InformationPostController.java index eb256013..8b5fb825 100644 --- a/back/src/main/java/com/back/domain/post/post/controller/InformationPostController.java +++ b/back/src/main/java/com/back/domain/post/post/controller/InformationPostController.java @@ -33,7 +33,8 @@ public RsData getPostWithPage( @RequestParam(defaultValue = "10") int size, @RequestParam(required = false) String keyword ) { - Page postPage = postService.getPosts(keyword, page,size); + Post.PostType postTyp = Post.PostType.INFORMATIONPOST; + Page postPage = postService.getPosts(keyword, page,size, postTyp); PostPagingResponse resDto = PostPagingResponse.from(postPage); return new RsData<>("200", "게시글이 조회 되었습니다.", resDto); diff --git a/back/src/main/java/com/back/domain/post/post/controller/QuestionPostController.java b/back/src/main/java/com/back/domain/post/post/controller/QuestionPostController.java new file mode 100644 index 00000000..f90331af --- /dev/null +++ b/back/src/main/java/com/back/domain/post/post/controller/QuestionPostController.java @@ -0,0 +1,137 @@ +package com.back.domain.post.post.controller; + +import com.back.domain.member.member.entity.Member; +import com.back.domain.post.like.service.PostLikeService; +import com.back.domain.post.post.dto.*; +import com.back.domain.post.post.entity.Post; +import com.back.domain.post.post.service.PostService; +import com.back.domain.post.rq.PostDetailFacade; +import com.back.global.rq.Rq; +import com.back.global.rsData.RsData; +import io.swagger.v3.oas.annotations.Operation; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/post/question") +@RequiredArgsConstructor +public class QuestionPostController { + + private final PostLikeService postLikeService; + private final PostService postService; + private final Rq rq; + private final PostDetailFacade postDetailFacade; + + + @Operation(summary = "질문 게시글 조회 - 페이징 처리") + @GetMapping + public RsData getPostWithPage( + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size, + @RequestParam(required = false) String keyword + ) { + Post.PostType postTyp = Post.PostType.QUESTIONPOST; + Page postPage = postService.getPosts(keyword, page,size,postTyp); + PostPagingResponse resDto = PostPagingResponse.from(postPage); + + return new RsData<>("200", "게시글이 조회 되었습니다.", resDto); + } + + @Operation(summary = "게시글 생성") + @PostMapping + public RsData createPost( + @Valid @RequestBody PostCreateRequest postCreateRequest + ) { + Member member = rq.getActor(); + Post post = postService.createPost(postCreateRequest, member); + PostCreateResponse postCreateResponse = PostCreateResponse.from(post); + + return new RsData<>("200", "게시글이 성공적으로 생성되었습니다.", postCreateResponse); + } + + @Operation(summary = "게시글 다건 조회") + @GetMapping("/all") + public RsData> getAllPost() { + List postAllResponse = postService.getAllPostResponse(); + + + return new RsData<>("200", "게시글 다건 조회 성공", postAllResponse); + } + + @Operation(summary = "게시글 단건 조회") + @GetMapping("/{post_id}") + public RsData getSinglePost(@PathVariable Long post_id) { + Post post = postService.findById(post_id); + + PostSingleResponse postSingleResponse = new PostSingleResponse(post); + + return new RsData<>("200", "게시글 단건 조회 성공", postSingleResponse); + } + + @Operation(summary = "게시글 삭제") + @DeleteMapping("/{post_id}") + public RsData removePost(@PathVariable Long post_id) { + Member member = rq.getActor(); + + postService.removePost(post_id, member); + + return new RsData<>("200", "게시글 삭제 성공", null); + } + + @Operation(summary = "게시글 수정") + @PutMapping("/{post_id}") + public RsData updatePost(@PathVariable Long post_id + ,@Valid @RequestBody PostCreateRequest postCreateRequest) { + Member member = rq.getActor(); + postService.updatePost(post_id, member, postCreateRequest); + + return new RsData<>("200", "게시글 수정 성공", null); + } + + @Operation(summary = "게시글 좋아요") + @PostMapping("/{post_id}/liked") + public RsData likePost(@PathVariable Long post_id) { + postLikeService.likePost(post_id); + + return new RsData<>("200", "게시글 좋아요 성공", null); + } + + @Operation(summary = "게시글 좋아요 (Show)") + @GetMapping("/{post_id}/liked") + public RsData getLike(@PathVariable Long post_id) { + int likeCount = postLikeService.getLikeCount(post_id); + PostLikedResponse postLikedResponse = new PostLikedResponse(likeCount); + + return new RsData<>("200", "게시글 좋아요 조회 성공", postLikedResponse); + } + + @Operation(summary = "게시글 싫어요 (Show)") + @GetMapping("/{post_id}/Disliked") + public RsData getDisLike(@PathVariable Long post_id) { + int likeCount = postLikeService.getDisLikeCount(post_id); + PostLikedResponse postLikedResponse = new PostLikedResponse(likeCount); + + return new RsData<>("200", "게시글 싫어요 조회 성공", postLikedResponse); + } + + @Operation(summary = "게시글 싫어요") + @PostMapping("/{post_id}/disliked") + public RsData disLikePost(@PathVariable Long post_id) { + postLikeService.disLikePost(post_id); + + return new RsData<>("200", "게시글 싫어요 성공", null); + } + + @GetMapping("/Detail/{post_id}") + public RsData getPostDetail(@PathVariable Long post_id) { + + PostDetailResponse response = postDetailFacade.getDetailWithViewIncrement(post_id); + + return new RsData<>("200", "게시글 상세 조회 성공", response); + } + +} diff --git a/back/src/main/java/com/back/domain/post/post/repository/PostRepositoryCustom.java b/back/src/main/java/com/back/domain/post/post/repository/PostRepositoryCustom.java index 52ed920d..201766cd 100644 --- a/back/src/main/java/com/back/domain/post/post/repository/PostRepositoryCustom.java +++ b/back/src/main/java/com/back/domain/post/post/repository/PostRepositoryCustom.java @@ -5,5 +5,5 @@ import org.springframework.data.domain.Pageable; public interface PostRepositoryCustom { - Page searchPosts(String keyword, Pageable pageable); + Page searchPosts(String keyword, Pageable pageable, Post.PostType postType); } diff --git a/back/src/main/java/com/back/domain/post/post/repository/PostRepositoryImpl.java b/back/src/main/java/com/back/domain/post/post/repository/PostRepositoryImpl.java index 77101a69..2aa6bef8 100644 --- a/back/src/main/java/com/back/domain/post/post/repository/PostRepositoryImpl.java +++ b/back/src/main/java/com/back/domain/post/post/repository/PostRepositoryImpl.java @@ -17,12 +17,15 @@ public class PostRepositoryImpl implements PostRepositoryCustom{ private final JPAQueryFactory queryFactory; @Override - public Page searchPosts(String keyword, Pageable pageable) { + public Page searchPosts(String keyword, Pageable pageable, Post.PostType postType) { QPost post = QPost.post; - QMember member = QMember.member; BooleanBuilder builder = new BooleanBuilder(); + if(postType != null) { + builder.and(post.postType.eq(postType)); + } + if(keyword != null && !keyword.isBlank()) { builder.and( post.title.containsIgnoreCase(keyword) @@ -34,7 +37,7 @@ public Page searchPosts(String keyword, Pageable pageable) { .selectFrom(post) .where(builder) .orderBy(post.createDate.desc()) - .offset(pageable.getOffset())// 이거뭐임 + .offset(pageable.getOffset()) // 시작점 .limit(pageable.getPageSize()) .fetch(); diff --git a/back/src/main/java/com/back/domain/post/post/service/PostService.java b/back/src/main/java/com/back/domain/post/post/service/PostService.java index 3ae4a355..67ec6e53 100644 --- a/back/src/main/java/com/back/domain/post/post/service/PostService.java +++ b/back/src/main/java/com/back/domain/post/post/service/PostService.java @@ -80,10 +80,10 @@ public Post getPostDetailWithViewIncrement(Long postId) { } - public Page getPosts(String keyword, int page, int size) { + public Page getPosts(String keyword, int page, int size ,Post.PostType postType) { Pageable pageable = PageRequest.of(page, size); - return postRepository.searchPosts(keyword, pageable).map(PostDto::from); + return postRepository.searchPosts(keyword, pageable, postType).map(PostDto::from); } public Post findById(Long postId) { diff --git a/back/src/test/java/com/back/domain/post/post/controller/InformationPostControllerTest.java b/back/src/test/java/com/back/domain/post/post/controller/InformationPostControllerTest.java index 7f21c119..fc3f7be9 100644 --- a/back/src/test/java/com/back/domain/post/post/controller/InformationPostControllerTest.java +++ b/back/src/test/java/com/back/domain/post/post/controller/InformationPostControllerTest.java @@ -197,7 +197,7 @@ void t3() throws Exception { .andExpect(status().isOk()) .andExpect(jsonPath("$.data").exists()) .andExpect(jsonPath("$.data.posts").isArray()) - .andExpect(jsonPath("$.data.currentPage").value(0)) + .andExpect(jsonPath("$.data.currentPage").value(1)) .andExpect(jsonPath("$.data.totalElements").exists()) .andExpect(jsonPath("$.msg").value("게시글이 조회 되었습니다.")); } From a3b1afa30f39cb298d3b8e79889c29a4a50fcecd Mon Sep 17 00:00:00 2001 From: luckhee Date: Fri, 26 Sep 2025 16:01:10 +0900 Subject: [PATCH 2/7] =?UTF-8?q?=EA=B2=8C=EC=8B=9C=EA=B8=80=20controller=20?= =?UTF-8?q?=ED=86=B5=ED=95=A9=20=EC=9E=91=EC=97=85=20-=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=95=20=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/QuestionPostController.java | 137 ------------------ 1 file changed, 137 deletions(-) delete mode 100644 back/src/main/java/com/back/domain/post/post/controller/QuestionPostController.java diff --git a/back/src/main/java/com/back/domain/post/post/controller/QuestionPostController.java b/back/src/main/java/com/back/domain/post/post/controller/QuestionPostController.java deleted file mode 100644 index f90331af..00000000 --- a/back/src/main/java/com/back/domain/post/post/controller/QuestionPostController.java +++ /dev/null @@ -1,137 +0,0 @@ -package com.back.domain.post.post.controller; - -import com.back.domain.member.member.entity.Member; -import com.back.domain.post.like.service.PostLikeService; -import com.back.domain.post.post.dto.*; -import com.back.domain.post.post.entity.Post; -import com.back.domain.post.post.service.PostService; -import com.back.domain.post.rq.PostDetailFacade; -import com.back.global.rq.Rq; -import com.back.global.rsData.RsData; -import io.swagger.v3.oas.annotations.Operation; -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -@RestController -@RequestMapping("/post/question") -@RequiredArgsConstructor -public class QuestionPostController { - - private final PostLikeService postLikeService; - private final PostService postService; - private final Rq rq; - private final PostDetailFacade postDetailFacade; - - - @Operation(summary = "질문 게시글 조회 - 페이징 처리") - @GetMapping - public RsData getPostWithPage( - @RequestParam(defaultValue = "0") int page, - @RequestParam(defaultValue = "10") int size, - @RequestParam(required = false) String keyword - ) { - Post.PostType postTyp = Post.PostType.QUESTIONPOST; - Page postPage = postService.getPosts(keyword, page,size,postTyp); - PostPagingResponse resDto = PostPagingResponse.from(postPage); - - return new RsData<>("200", "게시글이 조회 되었습니다.", resDto); - } - - @Operation(summary = "게시글 생성") - @PostMapping - public RsData createPost( - @Valid @RequestBody PostCreateRequest postCreateRequest - ) { - Member member = rq.getActor(); - Post post = postService.createPost(postCreateRequest, member); - PostCreateResponse postCreateResponse = PostCreateResponse.from(post); - - return new RsData<>("200", "게시글이 성공적으로 생성되었습니다.", postCreateResponse); - } - - @Operation(summary = "게시글 다건 조회") - @GetMapping("/all") - public RsData> getAllPost() { - List postAllResponse = postService.getAllPostResponse(); - - - return new RsData<>("200", "게시글 다건 조회 성공", postAllResponse); - } - - @Operation(summary = "게시글 단건 조회") - @GetMapping("/{post_id}") - public RsData getSinglePost(@PathVariable Long post_id) { - Post post = postService.findById(post_id); - - PostSingleResponse postSingleResponse = new PostSingleResponse(post); - - return new RsData<>("200", "게시글 단건 조회 성공", postSingleResponse); - } - - @Operation(summary = "게시글 삭제") - @DeleteMapping("/{post_id}") - public RsData removePost(@PathVariable Long post_id) { - Member member = rq.getActor(); - - postService.removePost(post_id, member); - - return new RsData<>("200", "게시글 삭제 성공", null); - } - - @Operation(summary = "게시글 수정") - @PutMapping("/{post_id}") - public RsData updatePost(@PathVariable Long post_id - ,@Valid @RequestBody PostCreateRequest postCreateRequest) { - Member member = rq.getActor(); - postService.updatePost(post_id, member, postCreateRequest); - - return new RsData<>("200", "게시글 수정 성공", null); - } - - @Operation(summary = "게시글 좋아요") - @PostMapping("/{post_id}/liked") - public RsData likePost(@PathVariable Long post_id) { - postLikeService.likePost(post_id); - - return new RsData<>("200", "게시글 좋아요 성공", null); - } - - @Operation(summary = "게시글 좋아요 (Show)") - @GetMapping("/{post_id}/liked") - public RsData getLike(@PathVariable Long post_id) { - int likeCount = postLikeService.getLikeCount(post_id); - PostLikedResponse postLikedResponse = new PostLikedResponse(likeCount); - - return new RsData<>("200", "게시글 좋아요 조회 성공", postLikedResponse); - } - - @Operation(summary = "게시글 싫어요 (Show)") - @GetMapping("/{post_id}/Disliked") - public RsData getDisLike(@PathVariable Long post_id) { - int likeCount = postLikeService.getDisLikeCount(post_id); - PostLikedResponse postLikedResponse = new PostLikedResponse(likeCount); - - return new RsData<>("200", "게시글 싫어요 조회 성공", postLikedResponse); - } - - @Operation(summary = "게시글 싫어요") - @PostMapping("/{post_id}/disliked") - public RsData disLikePost(@PathVariable Long post_id) { - postLikeService.disLikePost(post_id); - - return new RsData<>("200", "게시글 싫어요 성공", null); - } - - @GetMapping("/Detail/{post_id}") - public RsData getPostDetail(@PathVariable Long post_id) { - - PostDetailResponse response = postDetailFacade.getDetailWithViewIncrement(post_id); - - return new RsData<>("200", "게시글 상세 조회 성공", response); - } - -} From c3bd394fbb778c59d75de69af698a853fe8064a2 Mon Sep 17 00:00:00 2001 From: luckhee Date: Fri, 26 Sep 2025 16:01:26 +0900 Subject: [PATCH 3/7] =?UTF-8?q?=EA=B2=8C=EC=8B=9C=EA=B8=80=20controller=20?= =?UTF-8?q?=ED=86=B5=ED=95=A9=20=EC=9E=91=EC=97=85=20-=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=95=20=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/InformationPostController.java | 12 ++-- .../InformationPostControllerTest.java | 60 +++++++++---------- 2 files changed, 37 insertions(+), 35 deletions(-) diff --git a/back/src/main/java/com/back/domain/post/post/controller/InformationPostController.java b/back/src/main/java/com/back/domain/post/post/controller/InformationPostController.java index 8b5fb825..060e6a65 100644 --- a/back/src/main/java/com/back/domain/post/post/controller/InformationPostController.java +++ b/back/src/main/java/com/back/domain/post/post/controller/InformationPostController.java @@ -17,7 +17,7 @@ import java.util.List; @RestController -@RequestMapping("post/infor") +@RequestMapping("/post") @RequiredArgsConstructor public class InformationPostController { private final PostLikeService postLikeService; @@ -27,14 +27,14 @@ public class InformationPostController { @Operation(summary = "게시글 조회 - 페이징 처리") - @GetMapping + @GetMapping("/page/{postType}") public RsData getPostWithPage( + @PathVariable Post.PostType postType, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size, @RequestParam(required = false) String keyword ) { - Post.PostType postTyp = Post.PostType.INFORMATIONPOST; - Page postPage = postService.getPosts(keyword, page,size, postTyp); + Page postPage = postService.getPosts(keyword, page,size, postType); PostPagingResponse resDto = PostPagingResponse.from(postPage); return new RsData<>("200", "게시글이 조회 되었습니다.", resDto); @@ -109,7 +109,7 @@ public RsData getLike(@PathVariable Long post_id) { } @Operation(summary = "게시글 싫어요 (Show)") - @GetMapping("/{post_id}/Disliked") + @GetMapping("/{post_id}/disliked") public RsData getDisLike(@PathVariable Long post_id) { int likeCount = postLikeService.getDisLikeCount(post_id); PostLikedResponse postLikedResponse = new PostLikedResponse(likeCount); @@ -125,6 +125,8 @@ public RsData disLikePost(@PathVariable Long post_id) { return new RsData<>("200", "게시글 싫어요 성공", null); } + + @Operation(summary = "게시글 상세페이지") @GetMapping("/Detail/{post_id}") public RsData getPostDetail(@PathVariable Long post_id) { diff --git a/back/src/test/java/com/back/domain/post/post/controller/InformationPostControllerTest.java b/back/src/test/java/com/back/domain/post/post/controller/InformationPostControllerTest.java index fc3f7be9..51b2f2ac 100644 --- a/back/src/test/java/com/back/domain/post/post/controller/InformationPostControllerTest.java +++ b/back/src/test/java/com/back/domain/post/post/controller/InformationPostControllerTest.java @@ -75,7 +75,7 @@ void setUp() { void t1() throws Exception { ResultActions resultActions = mvc .perform( - post("/post/infor") + post("/post") .contentType(MediaType.APPLICATION_JSON) .content(""" { @@ -106,7 +106,7 @@ void t1() throws Exception { void t6() throws Exception { ResultActions resultActions = mvc .perform( - post("/post/infor") + post("/post") .contentType(MediaType.APPLICATION_JSON) .content(""" { @@ -133,7 +133,7 @@ void t6() throws Exception { void t2() throws Exception { ResultActions resultActions = mvc .perform( - post("/post/infor") + post("/post") .contentType(MediaType.APPLICATION_JSON) .content(""" { @@ -159,7 +159,7 @@ void t2() throws Exception { void t3() throws Exception { // 테스트용 게시글 먼저 생성 mvc.perform( - post("/post/infor") + post("/post") .contentType(MediaType.APPLICATION_JSON) .content(""" { @@ -172,7 +172,7 @@ void t3() throws Exception { ); mvc.perform( - post("/post/infor") + post("/post") .contentType(MediaType.APPLICATION_JSON) .content(""" { @@ -187,7 +187,7 @@ void t3() throws Exception { // 페이징 조회 테스트 - 기본값 ResultActions resultActions = mvc .perform( - get("/post/infor") + get("/post/page/{postType}", "INFORMATIONPOST") ) .andDo(print()); @@ -197,7 +197,7 @@ void t3() throws Exception { .andExpect(status().isOk()) .andExpect(jsonPath("$.data").exists()) .andExpect(jsonPath("$.data.posts").isArray()) - .andExpect(jsonPath("$.data.currentPage").value(1)) + .andExpect(jsonPath("$.data.currentPage").value(0)) .andExpect(jsonPath("$.data.totalElements").exists()) .andExpect(jsonPath("$.msg").value("게시글이 조회 되었습니다.")); } @@ -207,7 +207,7 @@ void t3() throws Exception { void t3_1() throws Exception { ResultActions resultActions = mvc .perform( - get("/post/infor") + get("/post/page/{postType}", "INFORMATIONPOST") .param("page", "0") .param("size", "5") ) @@ -228,7 +228,7 @@ void t3_1() throws Exception { void t3_2() throws Exception { // 검색 대상 게시글 생성 mvc.perform( - post("/post/infor") + post("/post") .contentType(MediaType.APPLICATION_JSON) .content(""" { @@ -242,7 +242,7 @@ void t3_2() throws Exception { ResultActions resultActions = mvc .perform( - get("/post/infor") + get("/post/page/{postType}", "INFORMATIONPOST") .param("keyword", "Spring") .param("page", "0") .param("size", "10") @@ -262,7 +262,7 @@ void t3_2() throws Exception { void t4() throws Exception { ResultActions resultActions = mvc .perform( - get("/post/infor/{post_id}", 1L) + get("/post/{post_id}", 1L) ) .andDo(print()); @@ -282,7 +282,7 @@ void t4() throws Exception { void t5() throws Exception { ResultActions resultActions = mvc .perform( - get("/post/infor/{post_id}", 999L) + get("/post/{post_id}", 999L) ) .andDo(print()); @@ -297,7 +297,7 @@ void t5() throws Exception { @DisplayName("게시글 삭제") void t7() throws Exception { mvc.perform( - post("/post/infor") + post("/post") .contentType(MediaType.APPLICATION_JSON) .content(""" { @@ -312,7 +312,7 @@ void t7() throws Exception { ResultActions resultActions = mvc .perform( - delete("/post/infor/{post_id}", 7L) + delete("/post/{post_id}", 7L) ) .andDo(print()); @@ -328,7 +328,7 @@ void t7() throws Exception { void t8() throws Exception { ResultActions resultActions = mvc .perform( - delete("/post/infor/{post_id}", 3L) + delete("/post/{post_id}", 3L) ) .andDo(print()); @@ -343,7 +343,7 @@ void t8() throws Exception { @DisplayName("게시글 수정") void t9() throws Exception { mvc.perform( - post("/post/infor") + post("/post") .contentType(MediaType.APPLICATION_JSON) .content(""" { @@ -358,7 +358,7 @@ void t9() throws Exception { ResultActions resultActions = mvc .perform( - put("/post/infor/{post_id}", 8L) + put("/post/{post_id}", 8L) .contentType(MediaType.APPLICATION_JSON) .content(""" { @@ -381,7 +381,7 @@ void t9() throws Exception { void t10() throws Exception { ResultActions resultActions = mvc .perform( - put("/post/infor/{post_id}", 2L) + put("/post/{post_id}", 2L) .contentType(MediaType.APPLICATION_JSON) .content(""" { @@ -404,7 +404,7 @@ void t10() throws Exception { void t11() throws Exception { ResultActions resultActions = mvc .perform( - put("/post/infor/{post_id}", 6L) + put("/post/{post_id}", 6L) .contentType(MediaType.APPLICATION_JSON) .content(""" { @@ -427,7 +427,7 @@ void t11() throws Exception { void t12() throws Exception { ResultActions resultActions = mvc .perform( - post("/post/infor/{post_id}/liked", 1L) + post("/post/{post_id}/liked", 1L) ) .andDo(print()); @@ -443,7 +443,7 @@ void t12() throws Exception { void t13() throws Exception { ResultActions resultActions = mvc .perform( - get("/post/infor/{post_id}/liked", 1L) + get("/post/{post_id}/liked", 1L) ) .andDo(print()); @@ -460,7 +460,7 @@ void t13() throws Exception { void t14() throws Exception { ResultActions resultActions = mvc .perform( - post("/post/infor/{post_id}/disliked", 1L) + post("/post/{post_id}/disliked", 1L) ) .andDo(print()); @@ -476,7 +476,7 @@ void t14() throws Exception { void t15() throws Exception { ResultActions resultActions = mvc .perform( - get("/post/infor/{post_id}/Disliked", 1L) + get("/post/{post_id}/disliked", 1L) ) .andDo(print()); @@ -492,13 +492,13 @@ void t15() throws Exception { @DisplayName("좋아요 -> 싫어요 토글 테스트") void t16() throws Exception { // 먼저 좋아요 - mvc.perform(post("/post/infor/{post_id}/liked", 1L)) + mvc.perform(post("/post/{post_id}/liked", 1L)) .andExpect(jsonPath("$.msg").value("게시글 좋아요 성공")); // 싫어요로 변경 ResultActions resultActions = mvc .perform( - post("/post/infor/{post_id}/disliked", 1L) + post("/post/{post_id}/disliked", 1L) ) .andDo(print()); @@ -513,13 +513,13 @@ void t16() throws Exception { @DisplayName("좋아요 중복 클릭 - 좋아요 취소") void t17() throws Exception { // 첫 번째 좋아요 - mvc.perform(post("/post/infor/{post_id}/liked", 1L)) + mvc.perform(post("/post/{post_id}/liked", 1L)) .andExpect(jsonPath("$.msg").value("게시글 좋아요 성공")); // 두 번째 좋아요 (취소) ResultActions resultActions = mvc .perform( - post("/post/infor/{post_id}/liked", 1L) + post("/post/{post_id}/liked", 1L) ) .andDo(print()); @@ -535,11 +535,11 @@ void t17() throws Exception { void t18() throws Exception { // 좋아요 추가하여 좋아요 정보도 함께 조회되는지 확인 - mvc.perform(post("/post/infor/{post_id}/liked", 1L)); + mvc.perform(post("/post/{post_id}/liked", 1L)); ResultActions resultActions = mvc .perform( - get("/post/infor/Detail/{post_id}", 1L) + get("/post/Detail/{post_id}", 1L) ) .andDo(print()); @@ -566,7 +566,7 @@ void t18() throws Exception { void t19() throws Exception { ResultActions resultActions = mvc .perform( - get("/post/infor/Detail/{post_id}", 999L) + get("/post/Detail/{post_id}", 999L) ) .andDo(print()); From 47e4f48f13ead58e3b51dedadb839b874d38bbf3 Mon Sep 17 00:00:00 2001 From: luckhee Date: Sat, 27 Sep 2025 21:55:24 +0900 Subject: [PATCH 4/7] =?UTF-8?q?Feat=20:=20PracticePost=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=EC=9E=90=20=EC=A0=9C=ED=95=9C=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?&&=20=EB=8C=93=EA=B8=80=20=EC=B1=84=ED=83=9D=EC=9D=84=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20isAdopted=20=EC=BB=AC=EB=9F=BC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/comment/entity/PostComment.java | 7 ++- ...ostController.java => PostController.java} | 2 +- .../back/domain/post/post/entity/Post.java | 4 ++ .../domain/post/post/service/PostService.java | 20 ++++++--- ...ollerTest.java => PostControllerTest.java} | 44 +++++++++---------- 5 files changed, 46 insertions(+), 31 deletions(-) rename back/src/main/java/com/back/domain/post/post/controller/{InformationPostController.java => PostController.java} (99%) rename back/src/test/java/com/back/domain/post/post/controller/{InformationPostControllerTest.java => PostControllerTest.java} (92%) diff --git a/back/src/main/java/com/back/domain/post/comment/entity/PostComment.java b/back/src/main/java/com/back/domain/post/comment/entity/PostComment.java index 2e800ca2..acb59117 100644 --- a/back/src/main/java/com/back/domain/post/comment/entity/PostComment.java +++ b/back/src/main/java/com/back/domain/post/comment/entity/PostComment.java @@ -27,6 +27,8 @@ public class PostComment extends BaseEntity { private String role; + private Boolean isAdopted; + public Boolean isAuthor( Member member) { return Objects.equals(this.member.getId(), member.getId()); } @@ -41,6 +43,7 @@ public PostComment(Post post, String content, Member member, String role) { this.content = content; this.member = member; this.role = role; + this.isAdopted = false; } @@ -55,5 +58,7 @@ public void updatePost(Post post) { this.post = post; } - + public void adoptComment() { + this.isAdopted = true; + } } diff --git a/back/src/main/java/com/back/domain/post/post/controller/InformationPostController.java b/back/src/main/java/com/back/domain/post/post/controller/PostController.java similarity index 99% rename from back/src/main/java/com/back/domain/post/post/controller/InformationPostController.java rename to back/src/main/java/com/back/domain/post/post/controller/PostController.java index 060e6a65..316a341e 100644 --- a/back/src/main/java/com/back/domain/post/post/controller/InformationPostController.java +++ b/back/src/main/java/com/back/domain/post/post/controller/PostController.java @@ -19,7 +19,7 @@ @RestController @RequestMapping("/post") @RequiredArgsConstructor -public class InformationPostController { +public class PostController { private final PostLikeService postLikeService; private final PostService postService; private final Rq rq; diff --git a/back/src/main/java/com/back/domain/post/post/entity/Post.java b/back/src/main/java/com/back/domain/post/post/entity/Post.java index 62bdc2ac..e598a93e 100644 --- a/back/src/main/java/com/back/domain/post/post/entity/Post.java +++ b/back/src/main/java/com/back/domain/post/post/entity/Post.java @@ -103,4 +103,8 @@ public void updateContent(String content) { public void increaseViewCount() { this.viewCount ++; } + + public void updateResolveStatus(Boolean isResolve) { + this.isResolve = isResolve; + } } diff --git a/back/src/main/java/com/back/domain/post/post/service/PostService.java b/back/src/main/java/com/back/domain/post/post/service/PostService.java index 67ec6e53..e7195e10 100644 --- a/back/src/main/java/com/back/domain/post/post/service/PostService.java +++ b/back/src/main/java/com/back/domain/post/post/service/PostService.java @@ -7,6 +7,7 @@ import com.back.domain.post.post.entity.Post; import com.back.domain.post.post.repository.PostRepository; import com.back.global.exception.ServiceException; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; @@ -19,6 +20,7 @@ @Service @RequiredArgsConstructor +@Tag(name = "PostController", description = "커뮤니티(게시글) API") public class PostService { private final PostRepository postRepository;; @@ -35,6 +37,11 @@ public Post createPost(PostCreateRequest postCreateRequest, Member member) { Post.validPostType(postTypeStr); Post.PostType postType = Post.PostType.valueOf(postTypeStr); + // PostType이 PracticePost인 경우 멘토인지 확인 + if (postType == Post.PostType.PRACTICEPOST && member.getRole() != Member.Role.MENTOR) { + throw new ServiceException("400", "실무 경험 공유 게시글은 멘토만 작성할 수 있습니다."); + } + Post post = Post.builder() .title(postCreateRequest.getTitle()) .content(postCreateRequest.getContent()) @@ -42,10 +49,11 @@ public Post createPost(PostCreateRequest postCreateRequest, Member member) { .postType(postType) .build(); -// post.setTitle(postCreateRequest.getTitle()); -// post.setContent(postCreateRequest.getContent()); -// post.setMember(member); -// post.setPostType(postType); + + // PostType이 QUESTIONPOST인 경우 isResolve를 false로 초기화 + if(postType == Post.PostType.QUESTIONPOST) { + post.updateResolveStatus(false); + } postRepository.save(post); @@ -79,7 +87,6 @@ public Post getPostDetailWithViewIncrement(Long postId) { return post; } - public Page getPosts(String keyword, int page, int size ,Post.PostType postType) { Pageable pageable = PageRequest.of(page, size); @@ -91,13 +98,12 @@ public Post findById(Long postId) { return post; } - - public List getAllPostResponse() { return postRepository.findAllWithMember().stream() .map(PostAllResponse::new) .toList(); } + //채택된 comment 받아오기 } diff --git a/back/src/test/java/com/back/domain/post/post/controller/InformationPostControllerTest.java b/back/src/test/java/com/back/domain/post/post/controller/PostControllerTest.java similarity index 92% rename from back/src/test/java/com/back/domain/post/post/controller/InformationPostControllerTest.java rename to back/src/test/java/com/back/domain/post/post/controller/PostControllerTest.java index 51b2f2ac..d7324086 100644 --- a/back/src/test/java/com/back/domain/post/post/controller/InformationPostControllerTest.java +++ b/back/src/test/java/com/back/domain/post/post/controller/PostControllerTest.java @@ -33,7 +33,7 @@ @SpringBootTest @AutoConfigureMockMvc @Transactional -public class InformationPostControllerTest { +public class PostControllerTest { @Autowired private PostService postService; @@ -92,7 +92,7 @@ void t1() throws Exception { Post createdPost = postService.findById(4L); resultActions - .andExpect(handler().handlerType(InformationPostController.class)) + .andExpect(handler().handlerType(PostController.class)) .andExpect(handler().methodName("createPost")) .andExpect(status().isOk()) .andExpect(jsonPath("$.msg").value("게시글이 성공적으로 생성되었습니다.")) @@ -121,7 +121,7 @@ void t6() throws Exception { resultActions - .andExpect(handler().handlerType(InformationPostController.class)) + .andExpect(handler().handlerType(PostController.class)) .andExpect(handler().methodName("createPost")) .andExpect(status().isBadRequest()) @@ -147,7 +147,7 @@ void t2() throws Exception { .andDo(print()); resultActions - .andExpect(handler().handlerType(InformationPostController.class)) + .andExpect(handler().handlerType(PostController.class)) .andExpect(handler().methodName("createPost")) .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.resultCode").value("400-2")) @@ -192,7 +192,7 @@ void t3() throws Exception { .andDo(print()); resultActions - .andExpect(handler().handlerType(InformationPostController.class)) + .andExpect(handler().handlerType(PostController.class)) .andExpect(handler().methodName("getPostWithPage")) .andExpect(status().isOk()) .andExpect(jsonPath("$.data").exists()) @@ -214,7 +214,7 @@ void t3_1() throws Exception { .andDo(print()); resultActions - .andExpect(handler().handlerType(InformationPostController.class)) + .andExpect(handler().handlerType(PostController.class)) .andExpect(handler().methodName("getPostWithPage")) .andExpect(status().isOk()) .andExpect(jsonPath("$.data.posts").isArray()) @@ -250,7 +250,7 @@ void t3_2() throws Exception { .andDo(print()); resultActions - .andExpect(handler().handlerType(InformationPostController.class)) + .andExpect(handler().handlerType(PostController.class)) .andExpect(handler().methodName("getPostWithPage")) .andExpect(status().isOk()) .andExpect(jsonPath("$.data.posts").isArray()) @@ -268,7 +268,7 @@ void t4() throws Exception { resultActions - .andExpect(handler().handlerType(InformationPostController.class)) + .andExpect(handler().handlerType(PostController.class)) .andExpect(handler().methodName("getSinglePost")) .andExpect(status().isOk()) .andExpect(jsonPath("$.msg").value("게시글 단건 조회 성공")) @@ -287,7 +287,7 @@ void t5() throws Exception { .andDo(print()); resultActions - .andExpect(handler().handlerType(InformationPostController.class)) + .andExpect(handler().handlerType(PostController.class)) .andExpect(handler().methodName("getSinglePost")) .andExpect(jsonPath("$.resultCode").value("400")) .andExpect(jsonPath("$.msg").value("해당 Id의 게시글이 없습니다.")); @@ -317,7 +317,7 @@ void t7() throws Exception { .andDo(print()); resultActions - .andExpect(handler().handlerType(InformationPostController.class)) + .andExpect(handler().handlerType(PostController.class)) .andExpect(handler().methodName("removePost")) .andExpect(jsonPath("$.resultCode").value("200")) .andExpect(jsonPath("$.msg").value("게시글 삭제 성공")); @@ -333,7 +333,7 @@ void t8() throws Exception { .andDo(print()); resultActions - .andExpect(handler().handlerType(InformationPostController.class)) + .andExpect(handler().handlerType(PostController.class)) .andExpect(handler().methodName("removePost")) .andExpect(jsonPath("$.resultCode").value("400")) .andExpect(jsonPath("$.msg").value("삭제 권한이 없습니다.")); @@ -370,7 +370,7 @@ void t9() throws Exception { .andDo(print()); resultActions - .andExpect(handler().handlerType(InformationPostController.class)) + .andExpect(handler().handlerType(PostController.class)) .andExpect(handler().methodName("updatePost")) .andExpect(jsonPath("$.resultCode").value("200")) .andExpect(jsonPath("$.msg").value("게시글 수정 성공")); @@ -393,7 +393,7 @@ void t10() throws Exception { .andDo(print()); resultActions - .andExpect(handler().handlerType(InformationPostController.class)) + .andExpect(handler().handlerType(PostController.class)) .andExpect(handler().methodName("updatePost")) .andExpect(jsonPath("$.resultCode").value("400")) .andExpect(jsonPath("$.msg").value("수정 권한이 없습니다.")); @@ -416,7 +416,7 @@ void t11() throws Exception { .andDo(print()); resultActions - .andExpect(handler().handlerType(InformationPostController.class)) + .andExpect(handler().handlerType(PostController.class)) .andExpect(handler().methodName("updatePost")) .andExpect(jsonPath("$.resultCode").value("400-1")) .andExpect(jsonPath("$.msg").value("title-NotBlank-제목은 null 혹은 공백일 수 없습니다.")); @@ -432,7 +432,7 @@ void t12() throws Exception { .andDo(print()); resultActions - .andExpect(handler().handlerType(InformationPostController.class)) + .andExpect(handler().handlerType(PostController.class)) .andExpect(handler().methodName("likePost")) .andExpect(jsonPath("$.resultCode").value("200")) .andExpect(jsonPath("$.msg").value("게시글 좋아요 성공")); @@ -448,7 +448,7 @@ void t13() throws Exception { .andDo(print()); resultActions - .andExpect(handler().handlerType(InformationPostController.class)) + .andExpect(handler().handlerType(PostController.class)) .andExpect(handler().methodName("getLike")) .andExpect(jsonPath("$.resultCode").value("200")) .andExpect(jsonPath("$.msg").value("게시글 좋아요 조회 성공")) @@ -465,7 +465,7 @@ void t14() throws Exception { .andDo(print()); resultActions - .andExpect(handler().handlerType(InformationPostController.class)) + .andExpect(handler().handlerType(PostController.class)) .andExpect(handler().methodName("disLikePost")) .andExpect(jsonPath("$.resultCode").value("200")) .andExpect(jsonPath("$.msg").value("게시글 싫어요 성공")); @@ -481,7 +481,7 @@ void t15() throws Exception { .andDo(print()); resultActions - .andExpect(handler().handlerType(InformationPostController.class)) + .andExpect(handler().handlerType(PostController.class)) .andExpect(handler().methodName("getDisLike")) .andExpect(jsonPath("$.resultCode").value("200")) .andExpect(jsonPath("$.msg").value("게시글 싫어요 조회 성공")) @@ -503,7 +503,7 @@ void t16() throws Exception { .andDo(print()); resultActions - .andExpect(handler().handlerType(InformationPostController.class)) + .andExpect(handler().handlerType(PostController.class)) .andExpect(handler().methodName("disLikePost")) .andExpect(jsonPath("$.resultCode").value("200")) .andExpect(jsonPath("$.msg").value("게시글 싫어요 성공")); @@ -524,7 +524,7 @@ void t17() throws Exception { .andDo(print()); resultActions - .andExpect(handler().handlerType(InformationPostController.class)) + .andExpect(handler().handlerType(PostController.class)) .andExpect(handler().methodName("likePost")) .andExpect(jsonPath("$.resultCode").value("200")) .andExpect(jsonPath("$.msg").value("게시글 좋아요 성공")); @@ -544,7 +544,7 @@ void t18() throws Exception { .andDo(print()); resultActions - .andExpect(handler().handlerType(InformationPostController.class)) + .andExpect(handler().handlerType(PostController.class)) .andExpect(handler().methodName("getPostDetail")) .andExpect(status().isOk()) .andExpect(jsonPath("$.resultCode").value("200")) @@ -571,7 +571,7 @@ void t19() throws Exception { .andDo(print()); resultActions - .andExpect(handler().handlerType(InformationPostController.class)) + .andExpect(handler().handlerType(PostController.class)) .andExpect(handler().methodName("getPostDetail")) .andExpect(jsonPath("$.resultCode").value("400")) .andExpect(jsonPath("$.msg").value("해당 Id의 게시글이 없습니다.")); From cd77e26b4d42986f0409300e4a40ccee1c1f8668 Mon Sep 17 00:00:00 2001 From: luckhee Date: Sun, 28 Sep 2025 00:33:13 +0900 Subject: [PATCH 5/7] =?UTF-8?q?Feat=20:=20QuestionPost=20Adopted=20API=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/PostCommentController.java | 7 +++++ .../post/comment/entity/PostComment.java | 7 +++++ .../repository/PostCommentRepository.java | 4 +++ .../comment/service/PostCommentService.java | 26 +++++++++++++++++++ 4 files changed, 44 insertions(+) diff --git a/back/src/main/java/com/back/domain/post/comment/controller/PostCommentController.java b/back/src/main/java/com/back/domain/post/comment/controller/PostCommentController.java index ad497b88..858acc2b 100644 --- a/back/src/main/java/com/back/domain/post/comment/controller/PostCommentController.java +++ b/back/src/main/java/com/back/domain/post/comment/controller/PostCommentController.java @@ -69,4 +69,11 @@ public RsData updatePostComment(@PathVariable Long post_id return new RsData<>("200", "댓글 수정 성공", null); } + @PostMapping("isAdopted/{commentId}") + public RsData adoptComment(@PathVariable Long commentId) { + Member member = rq.getActor(); + postCommentService.adoptComment(commentId, member); + return new RsData<>("200", "댓글 채택 성공", null); + } + } diff --git a/back/src/main/java/com/back/domain/post/comment/entity/PostComment.java b/back/src/main/java/com/back/domain/post/comment/entity/PostComment.java index acb59117..9520f8c3 100644 --- a/back/src/main/java/com/back/domain/post/comment/entity/PostComment.java +++ b/back/src/main/java/com/back/domain/post/comment/entity/PostComment.java @@ -61,4 +61,11 @@ public void updatePost(Post post) { public void adoptComment() { this.isAdopted = true; } +/* +* Post 단위 테스트 작성 +* isAdopted 테스트 +* PracticePost 작성 권한 테스트 +* API 명세서 수정 +* */ + } diff --git a/back/src/main/java/com/back/domain/post/comment/repository/PostCommentRepository.java b/back/src/main/java/com/back/domain/post/comment/repository/PostCommentRepository.java index d4c5ecc0..24b05542 100644 --- a/back/src/main/java/com/back/domain/post/comment/repository/PostCommentRepository.java +++ b/back/src/main/java/com/back/domain/post/comment/repository/PostCommentRepository.java @@ -1,6 +1,7 @@ package com.back.domain.post.comment.repository; import com.back.domain.post.comment.entity.PostComment; +import com.back.domain.post.post.entity.Post; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -13,4 +14,7 @@ public interface PostCommentRepository extends JpaRepository @Query("SELECT c FROM PostComment c JOIN FETCH c.member WHERE c.post.id = :postId") List findCommentsWithMemberByPostId(@Param("postId") Long postId); + + + boolean existsByPostAndIsAdoptedTrue(Post post); } diff --git a/back/src/main/java/com/back/domain/post/comment/service/PostCommentService.java b/back/src/main/java/com/back/domain/post/comment/service/PostCommentService.java index 74df7626..d4acbd96 100644 --- a/back/src/main/java/com/back/domain/post/comment/service/PostCommentService.java +++ b/back/src/main/java/com/back/domain/post/comment/service/PostCommentService.java @@ -95,5 +95,31 @@ private PostComment getPostCommentById(Long commentId) { } + public void adoptComment(Long commentId, Member member) { + PostComment postComment = postCommentRepository.findById(commentId) + .orElseThrow(() -> new ServiceException("400", "해당 Id의 댓글이 없습니다.")); + Post post = postComment.getPost(); + + if (!post.isAuthor(member)) { + throw new ServiceException("400", "채택 권한이 없습니다."); + } + + if (post.getPostType() != Post.PostType.QUESTIONPOST) { + throw new ServiceException("400", "질문 게시글에만 댓글 채택이 가능합니다."); + } + + if (postComment.getIsAdopted()) { + throw new ServiceException("400", "이미 채택된 댓글입니다."); + } + + // 이미 채택된 댓글이 있는지 확인 + // Post쪽에서 확인해야 하는건가 - No post는 comment를 관리할 책임이 없음. + boolean alreadyAdopted = postCommentRepository.existsByPostAndIsAdoptedTrue(post); + if (alreadyAdopted) { + throw new ServiceException("400", "이미 채택된 댓글이 있습니다."); + } + + postComment.adoptComment(); + } } From db64551eec0e8c6d3334c7ad8cdbfe98020513fd Mon Sep 17 00:00:00 2001 From: luckhee Date: Mon, 29 Sep 2025 09:22:02 +0900 Subject: [PATCH 6/7] =?UTF-8?q?Feat=20:=20PostComment=20=EC=9C=A0=ED=9A=A8?= =?UTF-8?q?=EC=84=B1=20=EA=B2=80=EC=A6=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/controller/PostCommentController.java | 2 +- .../post/comment/service/PostCommentService.java | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/back/src/main/java/com/back/domain/post/comment/controller/PostCommentController.java b/back/src/main/java/com/back/domain/post/comment/controller/PostCommentController.java index 858acc2b..ca785e38 100644 --- a/back/src/main/java/com/back/domain/post/comment/controller/PostCommentController.java +++ b/back/src/main/java/com/back/domain/post/comment/controller/PostCommentController.java @@ -68,7 +68,7 @@ public RsData updatePostComment(@PathVariable Long post_id return new RsData<>("200", "댓글 수정 성공", null); } - + @Operation(summary = "댓글 채택") @PostMapping("isAdopted/{commentId}") public RsData adoptComment(@PathVariable Long commentId) { Member member = rq.getActor(); diff --git a/back/src/main/java/com/back/domain/post/comment/service/PostCommentService.java b/back/src/main/java/com/back/domain/post/comment/service/PostCommentService.java index d4acbd96..3d1780d7 100644 --- a/back/src/main/java/com/back/domain/post/comment/service/PostCommentService.java +++ b/back/src/main/java/com/back/domain/post/comment/service/PostCommentService.java @@ -63,6 +63,10 @@ public void removePostComment(Long postId, CommentDeleteRequest commentDeleteReq throw new ServiceException("400", "삭제 권한이 없습니다."); } +// if(postComment.getIsAdopted()) { +// throw new ServiceException("400", "채택된 댓글은 삭제할 수 없습니다."); +// } + postCommentRepository.delete(postComment); } @@ -79,6 +83,10 @@ public void updatePostComment(Long postId, CommentModifyRequest commentModifyReq throw new ServiceException("400", "수정 권한이 없습니다."); } + if ( commentModifyRequest.getContent() == null || commentModifyRequest.getContent().isEmpty()) { + throw new ServiceException("400", "댓글은 비어 있을 수 없습니다."); + } + postComment.updateContent(commentModifyRequest.getContent()); } @@ -88,6 +96,10 @@ private void validatePostExists(Long postId) { if (!postRepository.existsById(postId)) { throw new ServiceException("400", "해당 Id의 게시글이 없습니다."); } + + if(postId == null || postId <= 0) { + throw new ServiceException("400", "유효하지 않은 게시글 Id입니다."); + } } private PostComment getPostCommentById(Long commentId) { @@ -114,7 +126,6 @@ public void adoptComment(Long commentId, Member member) { } // 이미 채택된 댓글이 있는지 확인 - // Post쪽에서 확인해야 하는건가 - No post는 comment를 관리할 책임이 없음. boolean alreadyAdopted = postCommentRepository.existsByPostAndIsAdoptedTrue(post); if (alreadyAdopted) { throw new ServiceException("400", "이미 채택된 댓글이 있습니다."); From 22bc404ba970ac98196d7029f5b1020f0cf42a12 Mon Sep 17 00:00:00 2001 From: luckhee Date: Mon, 29 Sep 2025 10:23:58 +0900 Subject: [PATCH 7/7] =?UTF-8?q?=20Test=20:=20Post,=20PostComment=20?= =?UTF-8?q?=EB=8B=A8=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=A7=84?= =?UTF-8?q?=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/service/PostCommentService.java | 8 +- .../back/domain/post/post/entity/Post.java | 6 +- .../domain/post/post/service/PostService.java | 14 + .../back/domain/post/rq/PostDetailFacade.java | 4 - .../post/comment/entity/PostCommentTest.java | 318 ++++++++++++ .../service/PostCommentServiceTest.java | 474 ++++++++++++++++++ .../post/controller/PostControllerTest.java | 2 +- .../domain/post/post/entity/PostTest.java | 352 +++++++++++++ .../post/post/service/PostServiceTest.java | 431 ++++++++++++++++ 9 files changed, 1598 insertions(+), 11 deletions(-) create mode 100644 back/src/test/java/com/back/domain/post/comment/entity/PostCommentTest.java create mode 100644 back/src/test/java/com/back/domain/post/comment/service/PostCommentServiceTest.java create mode 100644 back/src/test/java/com/back/domain/post/post/entity/PostTest.java create mode 100644 back/src/test/java/com/back/domain/post/post/service/PostServiceTest.java diff --git a/back/src/main/java/com/back/domain/post/comment/service/PostCommentService.java b/back/src/main/java/com/back/domain/post/comment/service/PostCommentService.java index 3d1780d7..9e803f05 100644 --- a/back/src/main/java/com/back/domain/post/comment/service/PostCommentService.java +++ b/back/src/main/java/com/back/domain/post/comment/service/PostCommentService.java @@ -93,13 +93,15 @@ public void updatePostComment(Long postId, CommentModifyRequest commentModifyReq private void validatePostExists(Long postId) { + if(postId == null || postId <= 0) { + throw new ServiceException("400", "유효하지 않은 게시글 Id입니다."); + } + if (!postRepository.existsById(postId)) { throw new ServiceException("400", "해당 Id의 게시글이 없습니다."); } - if(postId == null || postId <= 0) { - throw new ServiceException("400", "유효하지 않은 게시글 Id입니다."); - } + } private PostComment getPostCommentById(Long commentId) { diff --git a/back/src/main/java/com/back/domain/post/post/entity/Post.java b/back/src/main/java/com/back/domain/post/post/entity/Post.java index e598a93e..b44c49fc 100644 --- a/back/src/main/java/com/back/domain/post/post/entity/Post.java +++ b/back/src/main/java/com/back/domain/post/post/entity/Post.java @@ -82,20 +82,20 @@ public static void validPostType(String postTypeStr) { try { Post.PostType.valueOf(postTypeStr); } catch (IllegalArgumentException e) { - throw new ServiceException("400-2", "유효하지 않은 PostType입니다."); + throw new ServiceException("400", "유효하지 않은 PostType입니다."); } } public void updateTitle(String title) { if(title == null || title.isBlank()) { - throw new ServiceException("400-3", "제목은 null이거나 공백일 수 없습니다."); + throw new ServiceException("400", "제목은 null이거나 공백일 수 없습니다."); } this.title = title; } public void updateContent(String content) { if(content == null || content.isBlank()) { - throw new ServiceException("400-4", "내용은 null이거나 공백일 수 없습니다."); + throw new ServiceException("400", "내용은 null이거나 공백일 수 없습니다."); } this.content = content; } diff --git a/back/src/main/java/com/back/domain/post/post/service/PostService.java b/back/src/main/java/com/back/domain/post/post/service/PostService.java index e7195e10..417db201 100644 --- a/back/src/main/java/com/back/domain/post/post/service/PostService.java +++ b/back/src/main/java/com/back/domain/post/post/service/PostService.java @@ -42,6 +42,12 @@ public Post createPost(PostCreateRequest postCreateRequest, Member member) { throw new ServiceException("400", "실무 경험 공유 게시글은 멘토만 작성할 수 있습니다."); } +// if( postType == Post.PostType.PRACTICEPOST ) { +// if(member.getCareer() == null || member.getCareer().isEmpty()) { +// throw new ServiceException("400", "멘토는 경력을 입력해야 실무 경험 공유 게시글을 작성할 수 있습니다."); +// } +// } + Post post = Post.builder() .title(postCreateRequest.getTitle()) .content(postCreateRequest.getContent()) @@ -73,6 +79,14 @@ public void updatePost(long postId, Member member, @Valid PostCreateRequest post Post post = findById(postId); if (!post.isAuthor(member)) throw new ServiceException("400", "수정 권한이 없습니다."); + if ( postCreateRequest.getTitle() == null || postCreateRequest.getTitle().isBlank()) { + throw new ServiceException("400", "제목을 입력해주세요."); + } + + if ( postCreateRequest.getContent() == null || postCreateRequest.getContent().isBlank()) { + throw new ServiceException("400", "내용을 입력해주세요."); + } + post.updateTitle(postCreateRequest.getTitle()); post.updateContent(postCreateRequest.getContent()); diff --git a/back/src/main/java/com/back/domain/post/rq/PostDetailFacade.java b/back/src/main/java/com/back/domain/post/rq/PostDetailFacade.java index b1291426..11b67110 100644 --- a/back/src/main/java/com/back/domain/post/rq/PostDetailFacade.java +++ b/back/src/main/java/com/back/domain/post/rq/PostDetailFacade.java @@ -33,9 +33,5 @@ public PostDetailResponse getDetailWithViewIncrement(Long postId) { return PostDetailResponse.from(post, comments, likeCount, dislikeCount, userStatus); } -// public Post findById(Long postId) { -// return postService.findById(postId); -// } - } diff --git a/back/src/test/java/com/back/domain/post/comment/entity/PostCommentTest.java b/back/src/test/java/com/back/domain/post/comment/entity/PostCommentTest.java new file mode 100644 index 00000000..fc781b86 --- /dev/null +++ b/back/src/test/java/com/back/domain/post/comment/entity/PostCommentTest.java @@ -0,0 +1,318 @@ +package com.back.domain.post.comment.entity; + +import com.back.domain.member.member.entity.Member; +import com.back.domain.post.post.entity.Post; +import com.back.fixture.MemberFixture; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.*; + +class PostCommentTest { + + @Nested + @DisplayName("PostComment 생성 테스트") + class CreateCommentTest { + + @Test + @DisplayName("정상적인 PostComment 생성") + void createComment_success() { + // given + Member member = MemberFixture.createDefault(); + Post post = createDefaultPost(member); + String content = "테스트 댓글"; + String role = member.getRole().name(); + + // when + PostComment comment = PostComment.builder() + .post(post) + .content(content) + .member(member) + .role(role) + .build(); + + // then + assertThat(comment.getPost()).isEqualTo(post); + assertThat(comment.getContent()).isEqualTo(content); + assertThat(comment.getMember()).isEqualTo(member); + assertThat(comment.getRole()).isEqualTo(role); + assertThat(comment.getIsAdopted()).isFalse(); + } + + @Test + @DisplayName("댓글 생성 시 isAdopted는 기본적으로 false") + void createComment_defaultIsAdoptedFalse() { + // given + Member member = MemberFixture.createDefault(); + Post post = createDefaultPost(member); + + // when + PostComment comment = PostComment.builder() + .post(post) + .content("테스트 댓글") + .member(member) + .role(member.getRole().name()) + .build(); + + // then + assertThat(comment.getIsAdopted()).isFalse(); + } + } + + @Nested + @DisplayName("댓글 내용 업데이트 테스트") + class UpdateContentTest { + + @Test + @DisplayName("정상적인 댓글 내용 업데이트") + void updateContent_success() { + // given + Member member = MemberFixture.createDefault(); + PostComment comment = createDefaultComment(member); + String newContent = "수정된 댓글 내용"; + + // when + comment.updateContent(newContent); + + // then + assertThat(comment.getContent()).isEqualTo(newContent); + } + + @Test + @DisplayName("null 내용으로 업데이트 시 예외 발생") + void updateContent_withNull_throwsException() { + // given + Member member = MemberFixture.createDefault(); + PostComment comment = createDefaultComment(member); + + // when & then + assertThatThrownBy(() -> comment.updateContent(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("댓글을 입력해주세요"); + } + + @Test + @DisplayName("빈 문자열로 업데이트 시 예외 발생") + void updateContent_withEmpty_throwsException() { + // given + Member member = MemberFixture.createDefault(); + PostComment comment = createDefaultComment(member); + + // when & then + assertThatThrownBy(() -> comment.updateContent("")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("댓글을 입력해주세요"); + } + + @Test + @DisplayName("공백만 있는 문자열로 업데이트 시 예외 발생") + void updateContent_withWhitespace_throwsException() { + // given + Member member = MemberFixture.createDefault(); + PostComment comment = createDefaultComment(member); + + // when & then + assertThatThrownBy(() -> comment.updateContent(" ")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("댓글을 입력해주세요"); + } + } + + @Nested + @DisplayName("게시글 업데이트 테스트") + class UpdatePostTest { + + @Test + @DisplayName("댓글의 게시글 업데이트 성공") + void updatePost_success() { + // given + Member member = MemberFixture.createDefault(); + PostComment comment = createDefaultComment(member); + Post newPost = createDefaultPost(member); + + // when + comment.updatePost(newPost); + + // then + assertThat(comment.getPost()).isEqualTo(newPost); + } + + @Test + @DisplayName("댓글의 게시글을 null로 업데이트") + void updatePost_withNull() { + // given + Member member = MemberFixture.createDefault(); + PostComment comment = createDefaultComment(member); + + // when + comment.updatePost(null); + + // then + assertThat(comment.getPost()).isNull(); + } + } + + @Nested + @DisplayName("댓글 채택 테스트") + class AdoptCommentTest { + + @Test + @DisplayName("댓글 채택 성공") + void adoptComment_success() { + // given + Member member = MemberFixture.createDefault(); + PostComment comment = createDefaultComment(member); + + // when + comment.adoptComment(); + + // then + assertThat(comment.getIsAdopted()).isTrue(); + } + + @Test + @DisplayName("이미 채택된 댓글도 채택 가능") + void adoptComment_alreadyAdopted() { + // given + Member member = MemberFixture.createDefault(); + PostComment comment = createDefaultComment(member); + comment.adoptComment(); // 먼저 채택 + + // when + comment.adoptComment(); // 다시 채택 + + // then + assertThat(comment.getIsAdopted()).isTrue(); + } + } + + @Nested + @DisplayName("작성자 확인 테스트") + class AuthorCheckTest { + + @Test + @DisplayName("댓글 작성자 확인 성공") + void isAuthor_success() { + // given + Member author = MemberFixture.create(1L, "author@test.com", "Author", "password", Member.Role.MENTEE); + PostComment comment = createDefaultComment(author); + + // when + Boolean isAuthor = comment.isAuthor(author); + + // then + assertThat(isAuthor).isTrue(); + } + + @Test + @DisplayName("다른 사용자는 댓글 작성자가 아님") + void isAuthor_differentUser_false() { + // given + Member author = MemberFixture.create(1L, "author@test.com", "Author", "password", Member.Role.MENTEE); + Member otherUser = MemberFixture.create(2L, "other@test.com", "Other", "password", Member.Role.MENTEE); + PostComment comment = createDefaultComment(author); + + // when + Boolean isAuthor = comment.isAuthor(otherUser); + + // then + assertThat(isAuthor).isFalse(); + } + + @Test + @DisplayName("댓글 작성자 이름 반환") + void getAuthorName_success() { + // given + Member author = MemberFixture.create(1L, "author@test.com", "Author Name", "password", Member.Role.MENTEE); + PostComment comment = createDefaultComment(author); + + // when + String authorName = comment.getAuthorName(); + + // then + assertThat(authorName).isEqualTo("Author Name"); + } + } + + @Nested + @DisplayName("역할(Role) 테스트") + class RoleTest { + + @Test + @DisplayName("멘티 역할로 댓글 생성") + void createComment_withMenteeRole() { + // given + Member mentee = MemberFixture.create(1L, "mentee@test.com", "Mentee", "password", Member.Role.MENTEE); + Post post = createDefaultPost(mentee); + + // when + PostComment comment = PostComment.builder() + .post(post) + .content("멘티 댓글") + .member(mentee) + .role(mentee.getRole().name()) + .build(); + + // then + assertThat(comment.getRole()).isEqualTo("MENTEE"); + } + + @Test + @DisplayName("멘토 역할로 댓글 생성") + void createComment_withMentorRole() { + // given + Member mentor = MemberFixture.create(1L, "mentor@test.com", "Mentor", "password", Member.Role.MENTOR); + Post post = createDefaultPost(mentor); + + // when + PostComment comment = PostComment.builder() + .post(post) + .content("멘토 댓글") + .member(mentor) + .role(mentor.getRole().name()) + .build(); + + // then + assertThat(comment.getRole()).isEqualTo("MENTOR"); + } + + @Test + @DisplayName("관리자 역할로 댓글 생성") + void createComment_withAdminRole() { + // given + Member admin = MemberFixture.create(1L, "admin@test.com", "Admin", "password", Member.Role.ADMIN); + Post post = createDefaultPost(admin); + + // when + PostComment comment = PostComment.builder() + .post(post) + .content("관리자 댓글") + .member(admin) + .role(admin.getRole().name()) + .build(); + + // then + assertThat(comment.getRole()).isEqualTo("ADMIN"); + } + } + + private Post createDefaultPost(Member member) { + return Post.builder() + .title("테스트 게시글") + .content("테스트 내용") + .member(member) + .postType(Post.PostType.INFORMATIONPOST) + .build(); + } + + private PostComment createDefaultComment(Member member) { + Post post = createDefaultPost(member); + return PostComment.builder() + .post(post) + .content("테스트 댓글") + .member(member) + .role(member.getRole().name()) + .build(); + } +} \ No newline at end of file diff --git a/back/src/test/java/com/back/domain/post/comment/service/PostCommentServiceTest.java b/back/src/test/java/com/back/domain/post/comment/service/PostCommentServiceTest.java new file mode 100644 index 00000000..67df8bfe --- /dev/null +++ b/back/src/test/java/com/back/domain/post/comment/service/PostCommentServiceTest.java @@ -0,0 +1,474 @@ +package com.back.domain.post.comment.service; + +import com.back.domain.member.member.entity.Member; +import com.back.domain.post.comment.dto.CommentAllResponse; +import com.back.domain.post.comment.dto.CommentCreateRequest; +import com.back.domain.post.comment.dto.CommentDeleteRequest; +import com.back.domain.post.comment.dto.CommentModifyRequest; +import com.back.domain.post.comment.entity.PostComment; +import com.back.domain.post.comment.repository.PostCommentRepository; +import com.back.domain.post.post.entity.Post; +import com.back.domain.post.post.repository.PostRepository; +import com.back.fixture.MemberFixture; +import com.back.global.exception.ServiceException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class PostCommentServiceTest { + + @Mock + private PostRepository postRepository; + + @Mock + private PostCommentRepository postCommentRepository; + + @InjectMocks + private PostCommentService postCommentService; + + @Nested + @DisplayName("댓글 생성 테스트") + class CreateCommentTest { + + @Test + @DisplayName("댓글 생성 성공") + void createComment_success() { + // given + Member member = MemberFixture.create(1L, "user@test.com", "User", "password", Member.Role.MENTEE); + Post post = createDefaultPost(member); + Long postId = 1L; + CommentCreateRequest request = new CommentCreateRequest(); + request.setComment("테스트 댓글"); + + when(postRepository.findById(postId)).thenReturn(Optional.of(post)); + when(postCommentRepository.save(any(PostComment.class))).thenReturn(any(PostComment.class)); + + // when + postCommentService.createComment(member, postId, request); + + // then + verify(postRepository).findById(postId); + verify(postCommentRepository).save(any(PostComment.class)); + } + + @Test + @DisplayName("존재하지 않는 게시글에 댓글 생성 시 실패") + void createComment_postNotExists_failure() { + // given + Member member = MemberFixture.createDefault(); + Long postId = 999L; + CommentCreateRequest request = new CommentCreateRequest(); + request.setComment("테스트 댓글"); + + when(postRepository.findById(postId)).thenReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> postCommentService.createComment(member, postId, request)) + .isInstanceOf(ServiceException.class) + .hasMessage("400 : 해당 Id의 게시글이 없습니다."); + + verify(postCommentRepository, never()).save(any(PostComment.class)); + } + } + + @Nested + @DisplayName("댓글 조회 테스트") + class GetCommentTest { + + @Test + @DisplayName("게시글의 모든 댓글 조회 성공") + void getAllPostCommentResponse_success() { + // given + Member member1 = MemberFixture.create(1L, "user1@test.com", "User1", "password", Member.Role.MENTEE); + Member member2 = MemberFixture.create(2L, "user2@test.com", "User2", "password", Member.Role.MENTOR); + Post post = createDefaultPost(member1); + Long postId = 1L; + + List comments = Arrays.asList( + createComment(member1, post, "첫 번째 댓글"), + createComment(member2, post, "두 번째 댓글") + ); + + when(postRepository.existsById(postId)).thenReturn(true); + when(postCommentRepository.findCommentsWithMemberByPostId(postId)).thenReturn(comments); + + // when + List result = postCommentService.getAllPostCommentResponse(postId); + + // then + assertThat(result).hasSize(2); + verify(postRepository).existsById(postId); + verify(postCommentRepository).findCommentsWithMemberByPostId(postId); + } + + @Test + @DisplayName("존재하지 않는 게시글의 댓글 조회 시 실패") + void getAllPostCommentResponse_postNotExists_failure() { + // given + Long postId = 999L; + + when(postRepository.existsById(postId)).thenReturn(false); + + // when & then + assertThatThrownBy(() -> postCommentService.getAllPostCommentResponse(postId)) + .isInstanceOf(ServiceException.class) + .hasMessage("400 : 해당 Id의 게시글이 없습니다."); + + verify(postCommentRepository, never()).findCommentsWithMemberByPostId(anyLong()); + } + } + + @Nested + @DisplayName("댓글 삭제 테스트") + class RemoveCommentTest { + + @Test + @DisplayName("댓글 작성자가 댓글 삭제 성공") + void removePostComment_author_success() { + // given + Member author = MemberFixture.create(1L, "author@test.com", "Author", "password", Member.Role.MENTEE); + Post post = createDefaultPost(author); + PostComment comment = createComment(author, post, "삭제할 댓글"); + Long postId = 1L; + Long commentId = 1L; + CommentDeleteRequest request = new CommentDeleteRequest(); + request.setCommentId(commentId); + + when(postRepository.existsById(postId)).thenReturn(true); + when(postCommentRepository.findById(commentId)).thenReturn(Optional.of(comment)); + + // when + postCommentService.removePostComment(postId, request, author); + + // then + verify(postCommentRepository).delete(comment); + } + + @Test + @DisplayName("댓글 작성자가 아닌 사용자가 댓글 삭제 시도 시 실패") + void removePostComment_notAuthor_failure() { + // given + Member author = MemberFixture.create(1L, "author@test.com", "Author", "password", Member.Role.MENTEE); + Member otherUser = MemberFixture.create(2L, "other@test.com", "Other", "password", Member.Role.MENTEE); + Post post = createDefaultPost(author); + PostComment comment = createComment(author, post, "삭제할 댓글"); + Long postId = 1L; + Long commentId = 1L; + CommentDeleteRequest request = new CommentDeleteRequest(); + request.setCommentId(commentId); + + when(postRepository.existsById(postId)).thenReturn(true); + when(postCommentRepository.findById(commentId)).thenReturn(Optional.of(comment)); + + // when & then + assertThatThrownBy(() -> postCommentService.removePostComment(postId, request, otherUser)) + .isInstanceOf(ServiceException.class) + .hasMessage("400 : 삭제 권한이 없습니다."); + + verify(postCommentRepository, never()).delete(any(PostComment.class)); + } + + @Test + @DisplayName("존재하지 않는 댓글 삭제 시도 시 실패") + void removePostComment_commentNotExists_failure() { + // given + Member member = MemberFixture.createDefault(); + Long postId = 1L; + Long commentId = 999L; + CommentDeleteRequest request = new CommentDeleteRequest(); + request.setCommentId(commentId); + + when(postRepository.existsById(postId)).thenReturn(true); + when(postCommentRepository.findById(commentId)).thenReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> postCommentService.removePostComment(postId, request, member)) + .isInstanceOf(ServiceException.class) + .hasMessage("400 : 해당 Id의 댓글이 없습니다."); + + verify(postCommentRepository, never()).delete(any(PostComment.class)); + } + } + + @Nested + @DisplayName("댓글 수정 테스트") + class UpdateCommentTest { + + @Test + @DisplayName("댓글 작성자가 댓글 수정 성공") + void updatePostComment_author_success() { + // given + Member author = MemberFixture.create(1L, "author@test.com", "Author", "password", Member.Role.MENTEE); + Post post = createDefaultPost(author); + PostComment comment = createComment(author, post, "원본 댓글"); + Long postId = 1L; + Long commentId = 1L; + CommentModifyRequest request = new CommentModifyRequest(); + request.setCommentId(commentId); + request.setContent("수정된 댓글"); + + when(postRepository.existsById(postId)).thenReturn(true); + when(postCommentRepository.findById(commentId)).thenReturn(Optional.of(comment)); + + // when + postCommentService.updatePostComment(postId, request, author); + + // then + verify(postCommentRepository).findById(commentId); + } + + @Test + @DisplayName("댓글 작성자가 아닌 사용자가 댓글 수정 시도 시 실패") + void updatePostComment_notAuthor_failure() { + // given + Member author = MemberFixture.create(1L, "author@test.com", "Author", "password", Member.Role.MENTEE); + Member otherUser = MemberFixture.create(2L, "other@test.com", "Other", "password", Member.Role.MENTEE); + Post post = createDefaultPost(author); + PostComment comment = createComment(author, post, "원본 댓글"); + Long postId = 1L; + Long commentId = 1L; + + CommentModifyRequest request = new CommentModifyRequest(); + request.setCommentId(commentId); + request.setContent("400 : 수정된 댓글"); + + when(postRepository.existsById(postId)).thenReturn(true); + when(postCommentRepository.findById(commentId)).thenReturn(Optional.of(comment)); + + // when & then + assertThatThrownBy(() -> postCommentService.updatePostComment(postId, request, otherUser)) + .isInstanceOf(ServiceException.class) + .hasMessage("400 : 수정 권한이 없습니다."); + } + + @Test + @DisplayName("빈 내용으로 댓글 수정 시도 시 실패") + void updatePostComment_emptyContent_failure() { + // given + Member author = MemberFixture.create(1L, "author@test.com", "Author", "password", Member.Role.MENTEE); + Post post = createDefaultPost(author); + PostComment comment = createComment(author, post, "원본 댓글"); + Long postId = 1L; + Long commentId = 1L; + + CommentModifyRequest request = new CommentModifyRequest(); + request.setCommentId(commentId); + request.setContent(""); + + when(postRepository.existsById(postId)).thenReturn(true); + when(postCommentRepository.findById(commentId)).thenReturn(Optional.of(comment)); + + // when & then + assertThatThrownBy(() -> postCommentService.updatePostComment(postId, request, author)) + .isInstanceOf(ServiceException.class) + .hasMessage("400 : 댓글은 비어 있을 수 없습니다."); + } + + @Test + @DisplayName("null 내용으로 댓글 수정 시도 시 실패") + void updatePostComment_nullContent_failure() { + // given + Member author = MemberFixture.create(1L, "author@test.com", "Author", "password", Member.Role.MENTEE); + Post post = createDefaultPost(author); + PostComment comment = createComment(author, post, "원본 댓글"); + Long postId = 1L; + Long commentId = 1L; + CommentModifyRequest request = new CommentModifyRequest(); + request.setCommentId(commentId); + request.setContent(null); + + when(postRepository.existsById(postId)).thenReturn(true); + when(postCommentRepository.findById(commentId)).thenReturn(Optional.of(comment)); + + // when & then + assertThatThrownBy(() -> postCommentService.updatePostComment(postId, request, author)) + .isInstanceOf(ServiceException.class) + .hasMessage("400 : 댓글은 비어 있을 수 없습니다."); + } + } + + @Nested + @DisplayName("댓글 채택 테스트") + class AdoptCommentTest { + + @Test + @DisplayName("질문 게시글 작성자가 댓글 채택 성공") + void adoptComment_questionPostAuthor_success() { + // given + Member postAuthor = MemberFixture.create(1L, "author@test.com", "Author", "password", Member.Role.MENTEE); + Member commenter = MemberFixture.create(2L, "commenter@test.com", "Commenter", "password", Member.Role.MENTOR); + Post questionPost = createQuestionPost(postAuthor); + PostComment comment = createComment(commenter, questionPost, "답변 댓글"); + Long commentId = 1L; + + when(postCommentRepository.findById(commentId)).thenReturn(Optional.of(comment)); + when(postCommentRepository.existsByPostAndIsAdoptedTrue(questionPost)).thenReturn(false); + + // when + postCommentService.adoptComment(commentId, postAuthor); + + // then + verify(postCommentRepository).findById(commentId); + verify(postCommentRepository).existsByPostAndIsAdoptedTrue(questionPost); + } + + @Test + @DisplayName("게시글 작성자가 아닌 사용자가 댓글 채택 시도 시 실패") + void adoptComment_notPostAuthor_failure() { + // given + Member postAuthor = MemberFixture.create(1L, "author@test.com", "Author", "password", Member.Role.MENTEE); + Member commenter = MemberFixture.create(2L, "commenter@test.com", "Commenter", "password", Member.Role.MENTOR); + Member otherUser = MemberFixture.create(3L, "other@test.com", "Other", "password", Member.Role.MENTEE); + Post questionPost = createQuestionPost(postAuthor); + PostComment comment = createComment(commenter, questionPost, "답변 댓글"); + Long commentId = 1L; + + when(postCommentRepository.findById(commentId)).thenReturn(Optional.of(comment)); + + // when & then + assertThatThrownBy(() -> postCommentService.adoptComment(commentId, otherUser)) + .isInstanceOf(ServiceException.class) + .hasMessage("400 : 채택 권한이 없습니다."); + } + + @Test + @DisplayName("질문 게시글이 아닌 게시글의 댓글 채택 시도 시 실패") + void adoptComment_notQuestionPost_failure() { + // given + Member postAuthor = MemberFixture.create(1L, "author@test.com", "Author", "password", Member.Role.MENTEE); + Member commenter = MemberFixture.create(2L, "commenter@test.com", "Commenter", "password", Member.Role.MENTOR); + Post informationPost = createDefaultPost(postAuthor); + PostComment comment = createComment(commenter, informationPost, "일반 댓글"); + Long commentId = 1L; + + when(postCommentRepository.findById(commentId)).thenReturn(Optional.of(comment)); + + // when & then + assertThatThrownBy(() -> postCommentService.adoptComment(commentId, postAuthor)) + .isInstanceOf(ServiceException.class) + .hasMessage("400 : 질문 게시글에만 댓글 채택이 가능합니다."); + } + + @Test + @DisplayName("이미 채택된 댓글을 다시 채택 시도 시 실패") + void adoptComment_alreadyAdopted_failure() { + // given + Member postAuthor = MemberFixture.create(1L, "author@test.com", "Author", "password", Member.Role.MENTEE); + Member commenter = MemberFixture.create(2L, "commenter@test.com", "Commenter", "password", Member.Role.MENTOR); + Post questionPost = createQuestionPost(postAuthor); + PostComment comment = createComment(commenter, questionPost, "답변 댓글"); + comment.adoptComment(); // 이미 채택된 상태 + Long commentId = 1L; + + when(postCommentRepository.findById(commentId)).thenReturn(Optional.of(comment)); + + // when & then + assertThatThrownBy(() -> postCommentService.adoptComment(commentId, postAuthor)) + .isInstanceOf(ServiceException.class) + .hasMessage("400 : 이미 채택된 댓글입니다."); + } + + @Test + @DisplayName("이미 다른 댓글이 채택된 게시글에서 댓글 채택 시도 시 실패") + void adoptComment_anotherCommentAlreadyAdopted_failure() { + // given + Member postAuthor = MemberFixture.create(1L, "author@test.com", "Author", "password", Member.Role.MENTEE); + Member commenter = MemberFixture.create(2L, "commenter@test.com", "Commenter", "password", Member.Role.MENTOR); + Post questionPost = createQuestionPost(postAuthor); + PostComment comment = createComment(commenter, questionPost, "답변 댓글"); + Long commentId = 1L; + + when(postCommentRepository.findById(commentId)).thenReturn(Optional.of(comment)); + when(postCommentRepository.existsByPostAndIsAdoptedTrue(questionPost)).thenReturn(true); + + // when & then + assertThatThrownBy(() -> postCommentService.adoptComment(commentId, postAuthor)) + .isInstanceOf(ServiceException.class) + .hasMessage("400 : 이미 채택된 댓글이 있습니다."); + } + + @Test + @DisplayName("존재하지 않는 댓글 채택 시도 시 실패") + void adoptComment_commentNotExists_failure() { + // given + Member member = MemberFixture.createDefault(); + Long commentId = 999L; + + when(postCommentRepository.findById(commentId)).thenReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> postCommentService.adoptComment(commentId, member)) + .isInstanceOf(ServiceException.class) + .hasMessage("400 : 해당 Id의 댓글이 없습니다."); + } + } + + @Nested + @DisplayName("게시글 존재 검증 테스트") + class ValidatePostExistsTest { + + @Test + @DisplayName("null 게시글 ID 검증 실패") + void validatePostExists_nullId_failure() { + // given + Long postId = null; + + // when & then + assertThatThrownBy(() -> postCommentService.getAllPostCommentResponse(postId)) + .isInstanceOf(ServiceException.class) + .hasMessage("400 : 유효하지 않은 게시글 Id입니다."); + } + + @Test + @DisplayName("0 이하의 게시글 ID 검증 실패") + void validatePostExists_invalidId_failure() { + // given + Long postId = 0L; + + // when & then + assertThatThrownBy(() -> postCommentService.getAllPostCommentResponse(postId)) + .isInstanceOf(ServiceException.class) + .hasMessage("400 : 유효하지 않은 게시글 Id입니다."); + } + } + + private Post createDefaultPost(Member member) { + return Post.builder() + .title("테스트 게시글") + .content("테스트 내용") + .member(member) + .postType(Post.PostType.INFORMATIONPOST) + .build(); + } + + private Post createQuestionPost(Member member) { + return Post.builder() + .title("질문 게시글") + .content("질문 내용") + .member(member) + .postType(Post.PostType.QUESTIONPOST) + .build(); + } + + private PostComment createComment(Member member, Post post, String content) { + return PostComment.builder() + .post(post) + .content(content) + .member(member) + .role(member.getRole().name()) + .build(); + } +} diff --git a/back/src/test/java/com/back/domain/post/post/controller/PostControllerTest.java b/back/src/test/java/com/back/domain/post/post/controller/PostControllerTest.java index d7324086..f96b3d8c 100644 --- a/back/src/test/java/com/back/domain/post/post/controller/PostControllerTest.java +++ b/back/src/test/java/com/back/domain/post/post/controller/PostControllerTest.java @@ -150,7 +150,7 @@ void t2() throws Exception { .andExpect(handler().handlerType(PostController.class)) .andExpect(handler().methodName("createPost")) .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.resultCode").value("400-2")) + .andExpect(jsonPath("$.resultCode").value("400")) .andExpect(jsonPath("$.msg").value("유효하지 않은 PostType입니다.")); } diff --git a/back/src/test/java/com/back/domain/post/post/entity/PostTest.java b/back/src/test/java/com/back/domain/post/post/entity/PostTest.java new file mode 100644 index 00000000..48c4c258 --- /dev/null +++ b/back/src/test/java/com/back/domain/post/post/entity/PostTest.java @@ -0,0 +1,352 @@ +package com.back.domain.post.post.entity; + +import com.back.domain.member.member.entity.Member; +import com.back.domain.post.comment.entity.PostComment; +import com.back.fixture.MemberFixture; +import com.back.global.exception.ServiceException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.*; + +class PostTest { + + @Nested + @DisplayName("Post 생성 테스트") + class CreatePostTest { + + @Test + @DisplayName("정상적인 Post 생성") + void createPost_success() { + // given + Member member = MemberFixture.createDefault(); + String title = "테스트 제목"; + String content = "테스트 내용"; + Post.PostType postType = Post.PostType.INFORMATIONPOST; + + // when + Post post = Post.builder() + .title(title) + .content(content) + .member(member) + .postType(postType) + .build(); + + // then + assertThat(post.getTitle()).isEqualTo(title); + assertThat(post.getContent()).isEqualTo(content); + assertThat(post.getMember()).isEqualTo(member); + assertThat(post.getPostType()).isEqualTo(postType); + assertThat(post.getViewCount()).isEqualTo(0); + assertThat(post.getComments()).isEmpty(); + } + + @Test + @DisplayName("댓글 리스트가 null일 때 빈 리스트로 초기화") + void createPost_withNullComments_initializeEmptyList() { + // given + Member member = MemberFixture.createDefault(); + + // when + Post post = Post.builder() + .title("제목") + .content("내용") + .member(member) + .postType(Post.PostType.INFORMATIONPOST) + .comments(null) + .build(); + + // then + assertThat(post.getComments()).isNotNull(); + assertThat(post.getComments()).isEmpty(); + } + } + + @Nested + @DisplayName("PostType 검증 테스트") + class ValidPostTypeTest { + + @Test + @DisplayName("유효한 PostType 검증 성공") + void validPostType_success() { + // given & when & then + assertThatNoException().isThrownBy(() -> Post.validPostType("INFORMATIONPOST")); + assertThatNoException().isThrownBy(() -> Post.validPostType("PRACTICEPOST")); + assertThatNoException().isThrownBy(() -> Post.validPostType("QUESTIONPOST")); + } + + @Test + @DisplayName("유효하지 않은 PostType 검증 실패") + void validPostType_failure() { + // given + String invalidPostType = "INVALIDPOST"; + + // when & then + assertThatThrownBy(() -> Post.validPostType(invalidPostType)) + .isInstanceOf(ServiceException.class) + .hasMessage("400 : 유효하지 않은 PostType입니다."); + } + } + + @Nested + @DisplayName("게시글 업데이트 테스트") + class UpdatePostTest { + + @Test + @DisplayName("제목 업데이트 성공") + void updateTitle_success() { + // given + Member member = MemberFixture.createDefault(); + Post post = createDefaultPost(member); + String newTitle = "새로운 제목"; + + // when + post.updateTitle(newTitle); + + // then + assertThat(post.getTitle()).isEqualTo(newTitle); + } + + @Test + @DisplayName("null 제목으로 업데이트 실패") + void updateTitle_withNull_failure() { + // given + Member member = MemberFixture.createDefault(); + Post post = createDefaultPost(member); + + // when & then + assertThatThrownBy(() -> post.updateTitle(null)) + .isInstanceOf(ServiceException.class) + .hasMessage("400 : 제목은 null이거나 공백일 수 없습니다."); + } + + @Test + @DisplayName("공백 제목으로 업데이트 실패") + void updateTitle_withBlank_failure() { + // given + Member member = MemberFixture.createDefault(); + Post post = createDefaultPost(member); + + // when & then + assertThatThrownBy(() -> post.updateTitle(" ")) + .isInstanceOf(ServiceException.class) + .hasMessage("400 : 제목은 null이거나 공백일 수 없습니다."); + } + + @Test + @DisplayName("내용 업데이트 성공") + void updateContent_success() { + // given + Member member = MemberFixture.createDefault(); + Post post = createDefaultPost(member); + String newContent = "새로운 내용"; + + // when + post.updateContent(newContent); + + // then + assertThat(post.getContent()).isEqualTo(newContent); + } + + @Test + @DisplayName("null 내용으로 업데이트 실패") + void updateContent_withNull_failure() { + // given + Member member = MemberFixture.createDefault(); + Post post = createDefaultPost(member); + + // when & then + assertThatThrownBy(() -> post.updateContent(null)) + .isInstanceOf(ServiceException.class) + .hasMessage("400 : 내용은 null이거나 공백일 수 없습니다."); + } + + @Test + @DisplayName("공백 내용으로 업데이트 실패") + void updateContent_withBlank_failure() { + // given + Member member = MemberFixture.createDefault(); + Post post = createDefaultPost(member); + + // when & then + assertThatThrownBy(() -> post.updateContent(" ")) + .isInstanceOf(ServiceException.class) + .hasMessage("400 : 내용은 null이거나 공백일 수 없습니다."); + } + } + + @Nested + @DisplayName("댓글 관리 테스트") + class CommentManagementTest { + + @Test + @DisplayName("댓글 추가 성공") + void addComment_success() { + // given + Member member = MemberFixture.createDefault(); + Post post = createDefaultPost(member); + PostComment comment = createComment(member, post); + + // when + post.addComment(comment); + + // then + assertThat(post.getComments()).hasSize(1); + assertThat(post.getComments()).contains(comment); + assertThat(comment.getPost()).isEqualTo(post); + } + + @Test + @DisplayName("댓글 제거 성공") + void removeComment_success() { + // given + Member member = MemberFixture.createDefault(); + Post post = createDefaultPost(member); + PostComment comment = createComment(member, post); + post.addComment(comment); + + // when + post.removeComment(comment); + + // then + assertThat(post.getComments()).isEmpty(); + assertThat(comment.getPost()).isNull(); + } + } + + @Nested + @DisplayName("작성자 확인 테스트") + class AuthorCheckTest { + + @Test + @DisplayName("작성자 확인 성공") + void isAuthor_success() { + // given + Member author = MemberFixture.create(1L, "author@test.com", "Author", "password", Member.Role.MENTEE); + Post post = createDefaultPost(author); + + // when + Boolean isAuthor = post.isAuthor(author); + + // then + assertThat(isAuthor).isTrue(); + } + + @Test + @DisplayName("다른 사용자는 작성자가 아님") + void isAuthor_differentUser_false() { + // given + Member author = MemberFixture.create(1L, "author@test.com", "Author", "password", Member.Role.MENTEE); + Member otherUser = MemberFixture.create(2L, "other@test.com", "Other", "password", Member.Role.MENTEE); + Post post = createDefaultPost(author); + + // when + Boolean isAuthor = post.isAuthor(otherUser); + + // then + assertThat(isAuthor).isFalse(); + } + + @Test + @DisplayName("작성자 이름 반환") + void getAuthorName_success() { + // given + Member author = MemberFixture.create(1L, "author@test.com", "Author Name", "password", Member.Role.MENTEE); + Post post = createDefaultPost(author); + + // when + String authorName = post.getAuthorName(); + + // then + assertThat(authorName).isEqualTo("Author Name"); + } + } + + @Nested + @DisplayName("조회수 증가 테스트") + class ViewCountTest { + + @Test + @DisplayName("조회수 증가 성공") + void increaseViewCount_success() { + // given + Member member = MemberFixture.createDefault(); + Post post = createDefaultPost(member); + int initialViewCount = post.getViewCount(); + + // when + post.increaseViewCount(); + + // then + assertThat(post.getViewCount()).isEqualTo(initialViewCount + 1); + } + + @Test + @DisplayName("조회수 여러 번 증가") + void increaseViewCount_multiple_times() { + // given + Member member = MemberFixture.createDefault(); + Post post = createDefaultPost(member); + + // when + post.increaseViewCount(); + post.increaseViewCount(); + post.increaseViewCount(); + + // then + assertThat(post.getViewCount()).isEqualTo(3); + } + } + + @Nested + @DisplayName("해결 상태 업데이트 테스트") + class ResolveStatusTest { + + @Test + @DisplayName("해결 상태를 true로 업데이트") + void updateResolveStatus_true() { + // given + Member member = MemberFixture.createDefault(); + Post post = createDefaultPost(member); + + // when + post.updateResolveStatus(true); + + // then + assertThat(post.getIsResolve()).isTrue(); + } + + @Test + @DisplayName("해결 상태를 false로 업데이트") + void updateResolveStatus_false() { + // given + Member member = MemberFixture.createDefault(); + Post post = createDefaultPost(member); + + // when + post.updateResolveStatus(false); + + // then + assertThat(post.getIsResolve()).isFalse(); + } + } + + private Post createDefaultPost(Member member) { + return Post.builder() + .title("테스트 제목") + .content("테스트 내용") + .member(member) + .postType(Post.PostType.INFORMATIONPOST) + .build(); + } + + private PostComment createComment(Member member, Post post) { + return PostComment.builder() + .post(post) + .content("테스트 댓글") + .member(member) + .role(member.getRole().name()) + .build(); + } +} diff --git a/back/src/test/java/com/back/domain/post/post/service/PostServiceTest.java b/back/src/test/java/com/back/domain/post/post/service/PostServiceTest.java new file mode 100644 index 00000000..1f2c6456 --- /dev/null +++ b/back/src/test/java/com/back/domain/post/post/service/PostServiceTest.java @@ -0,0 +1,431 @@ +package com.back.domain.post.post.service; + +import com.back.domain.member.member.entity.Member; +import com.back.domain.post.post.dto.PostCreateRequest; +import com.back.domain.post.post.dto.PostDto; +import com.back.domain.post.post.entity.Post; +import com.back.domain.post.post.repository.PostRepository; +import com.back.fixture.MemberFixture; +import com.back.global.exception.ServiceException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class PostServiceTest { + + @Mock + private PostRepository postRepository; + + @InjectMocks + private PostService postService; + + @Nested + @DisplayName("게시글 생성 테스트") + class CreatePostTest { + + @Test + @DisplayName("정보 공유 게시글 생성 성공") + void createPost_informationPost_success() { + // given + Member member = MemberFixture.create(1L, "test@test.com", "Test User", "password", Member.Role.MENTEE); + PostCreateRequest request = new PostCreateRequest(); + request.setContent("내용"); + request.setTitle("제목"); + request.setPostType("INFORMATIONPOST"); + + + Post expectedPost = createPost("제목", "내용", member, Post.PostType.INFORMATIONPOST); + + when(postRepository.save(any(Post.class))).thenReturn(expectedPost); + + // when + Post result = postService.createPost(request, member); + + // then + assertThat(result.getTitle()).isEqualTo("제목"); + assertThat(result.getContent()).isEqualTo("내용"); + assertThat(result.getMember()).isEqualTo(member); + assertThat(result.getPostType()).isEqualTo(Post.PostType.INFORMATIONPOST); + verify(postRepository).save(any(Post.class)); + } + + @Test + @DisplayName("멘토가 실무 경험 공유 게시글 생성 성공") + void createPost_practicePost_mentor_success() { + // given + Member mentor = MemberFixture.create(1L, "mentor@test.com", "Mentor", "password", Member.Role.MENTOR); + PostCreateRequest request = new PostCreateRequest(); + request.setContent("실무내용"); + request.setTitle("실무경험"); + request.setPostType("PRACTICEPOST"); + Post expectedPost = createPost("실무 경험", "실무 내용", mentor, Post.PostType.PRACTICEPOST); + + when(postRepository.save(any(Post.class))).thenReturn(expectedPost); + + // when + Post result = postService.createPost(request, mentor); + + // then + assertThat(result.getPostType()).isEqualTo(Post.PostType.PRACTICEPOST); + verify(postRepository).save(any(Post.class)); + } + + @Test + @DisplayName("멘티가 실무 경험 공유 게시글 생성 실패") + void createPost_practicePost_mentee_failure() { + // given + Member mentee = MemberFixture.create(1L, "mentee@test.com", "Mentee", "password", Member.Role.MENTEE); + PostCreateRequest request = new PostCreateRequest(); + request.setContent("실무내용"); + request.setTitle("실무경험"); + request.setPostType("PRACTICEPOST"); + + // when & then + assertThatThrownBy(() -> postService.createPost(request, mentee)) + .isInstanceOf(ServiceException.class) + .hasMessage("400 : 실무 경험 공유 게시글은 멘토만 작성할 수 있습니다."); + + verify(postRepository, never()).save(any(Post.class)); + } + + @Test + @DisplayName("질문 게시글 생성 시 isResolve false로 초기화") + void createPost_questionPost_initializeIsResolve() { + // given + Member member = MemberFixture.create(1L, "test@test.com", "Test User", "password", Member.Role.MENTEE); + PostCreateRequest request = new PostCreateRequest(); + request.setContent("질문내용"); + request.setTitle("질문경험"); + request.setPostType("QUESTIONPOST"); + Post expectedPost = createPost("질문", "질문 내용", member, Post.PostType.QUESTIONPOST); + expectedPost.updateResolveStatus(false); + + when(postRepository.save(any(Post.class))).thenReturn(expectedPost); + + // when + Post result = postService.createPost(request, member); + + // then + assertThat(result.getPostType()).isEqualTo(Post.PostType.QUESTIONPOST); + assertThat(result.getIsResolve()).isFalse(); + verify(postRepository).save(any(Post.class)); + } + + @Test + @DisplayName("유효하지 않은 PostType으로 게시글 생성 실패") + void createPost_invalidPostType_failure() { + // given + Member member = MemberFixture.createDefault(); + PostCreateRequest request = new PostCreateRequest(); + request.setContent("실무내용"); + request.setTitle("실무경험"); + request.setPostType("INVALIDPOST"); + + // when & then + assertThatThrownBy(() -> postService.createPost(request, member)) + .isInstanceOf(ServiceException.class) + .hasMessage("400 : 유효하지 않은 PostType입니다."); + + verify(postRepository, never()).save(any(Post.class)); + } + } + + @Nested + @DisplayName("게시글 삭제 테스트") + class RemovePostTest { + + @Test + @DisplayName("작성자가 게시글 삭제 성공") + void removePost_author_success() { + // given + Member author = MemberFixture.create(1L, "author@test.com", "Author", "password", Member.Role.MENTEE); + Post post = createPost("제목", "내용", author, Post.PostType.INFORMATIONPOST); + Long postId = 1L; + + when(postRepository.findById(postId)).thenReturn(Optional.of(post)); + + // when + postService.removePost(postId, author); + + // then + verify(postRepository).delete(post); + } + + @Test + @DisplayName("작성자가 아닌 사용자가 게시글 삭제 시도 시 실패") + void removePost_notAuthor_failure() { + // given + Member author = MemberFixture.create(1L, "author@test.com", "Author", "password", Member.Role.MENTEE); + Member otherUser = MemberFixture.create(2L, "other@test.com", "Other", "password", Member.Role.MENTEE); + Post post = createPost("제목", "내용", author, Post.PostType.INFORMATIONPOST); + Long postId = 1L; + + when(postRepository.findById(postId)).thenReturn(Optional.of(post)); + + // when & then + assertThatThrownBy(() -> postService.removePost(postId, otherUser)) + .isInstanceOf(ServiceException.class) + .hasMessage("400 : 삭제 권한이 없습니다."); + + verify(postRepository, never()).delete(any(Post.class)); + } + + @Test + @DisplayName("존재하지 않는 게시글 삭제 시도 시 실패") + void removePost_notExists_failure() { + // given + Member member = MemberFixture.createDefault(); + Long postId = 999L; + + when(postRepository.findById(postId)).thenReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> postService.removePost(postId, member)) + .isInstanceOf(ServiceException.class) + .hasMessage("400 : 해당 Id의 게시글이 없습니다."); + + verify(postRepository, never()).delete(any(Post.class)); + } + } + + @Nested + @DisplayName("게시글 수정 테스트") + class UpdatePostTest { + + @Test + @DisplayName("작성자가 게시글 수정 성공") + void updatePost_author_success() { + // given + Member author = MemberFixture.create(1L, "author@test.com", "Author", "password", Member.Role.MENTEE); + Post post = createPost("기존 제목", "기존 내용", author, Post.PostType.INFORMATIONPOST); + Long postId = 1L; + PostCreateRequest updateRequest = new PostCreateRequest(); + updateRequest.setTitle("새 제목"); + updateRequest.setContent("새 내용"); + updateRequest.setPostType("INFORMATIONPOST"); + + when(postRepository.findById(postId)).thenReturn(Optional.of(post)); + when(postRepository.save(any(Post.class))).thenReturn(post); + + // when + postService.updatePost(postId, author, updateRequest); + + // then + verify(postRepository).save(post); + assertThat(post.getTitle()).isEqualTo("새 제목"); + assertThat(post.getContent()).isEqualTo("새 내용"); + } + + @Test + @DisplayName("작성자가 아닌 사용자가 게시글 수정 시도 시 실패") + void updatePost_notAuthor_failure() { + // given + Member author = MemberFixture.create(1L, "author@test.com", "Author", "password", Member.Role.MENTEE); + Member otherUser = MemberFixture.create(2L, "other@test.com", "Other", "password", Member.Role.MENTEE); + Post post = createPost("제목", "내용", author, Post.PostType.INFORMATIONPOST); + Long postId = 1L; + PostCreateRequest updateRequest = new PostCreateRequest(); + updateRequest.setTitle("새 제목"); + updateRequest.setContent("새 내용"); + updateRequest.setPostType("INFORMATIONPOST"); + + when(postRepository.findById(postId)).thenReturn(Optional.of(post)); + + // when & then + assertThatThrownBy(() -> postService.updatePost(postId, otherUser, updateRequest)) + .isInstanceOf(ServiceException.class) + .hasMessage("400 : 수정 권한이 없습니다."); + + verify(postRepository, never()).save(any(Post.class)); + } + + @Test + @DisplayName("제목이 null이거나 공백일 때 수정 실패") + void updatePost_nullOrBlankTitle_failure() { + // given + Member author = MemberFixture.create(1L, "author@test.com", "Author", "password", Member.Role.MENTEE); + Post post = createPost("제목", "내용", author, Post.PostType.INFORMATIONPOST); + Long postId = 1L; + PostCreateRequest updateRequest = new PostCreateRequest(); + updateRequest.setTitle(""); + updateRequest.setContent("새 내용"); + updateRequest.setPostType("INFORMATIONPOST"); + + when(postRepository.findById(postId)).thenReturn(Optional.of(post)); + + // when & then + assertThatThrownBy(() -> postService.updatePost(postId, author, updateRequest)) + .isInstanceOf(ServiceException.class) + .hasMessage("400 : 제목을 입력해주세요."); + + verify(postRepository, never()).save(any(Post.class)); + } + + @Test + @DisplayName("내용이 null이거나 공백일 때 수정 실패") + void updatePost_nullOrBlankContent_failure() { + // given + Member author = MemberFixture.create(1L, "author@test.com", "Author", "password", Member.Role.MENTEE); + Post post = createPost("제목", "내용", author, Post.PostType.INFORMATIONPOST); + Long postId = 1L; + PostCreateRequest updateRequest = new PostCreateRequest(); + updateRequest.setTitle("새 제목"); + updateRequest.setContent(""); + updateRequest.setPostType("INFORMATIONPOST"); + + when(postRepository.findById(postId)).thenReturn(Optional.of(post)); + + // when & then + assertThatThrownBy(() -> postService.updatePost(postId, author, updateRequest)) + .isInstanceOf(ServiceException.class) + .hasMessage("400 : 내용을 입력해주세요."); + + verify(postRepository, never()).save(any(Post.class)); + } + } + + @Nested + @DisplayName("게시글 조회 테스트") + class GetPostTest { + + @Test + @DisplayName("모든 게시글 조회 성공") + void getAllPosts_success() { + // given + Member member1 = MemberFixture.create(1L, "user1@test.com", "User1", "password", Member.Role.MENTEE); + Member member2 = MemberFixture.create(2L, "user2@test.com", "User2", "password", Member.Role.MENTOR); + + List posts = Arrays.asList( + createPost("제목1", "내용1", member1, Post.PostType.INFORMATIONPOST), + createPost("제목2", "내용2", member2, Post.PostType.PRACTICEPOST) + ); + + when(postRepository.findAll()).thenReturn(posts); + + // when + List result = postService.getAllPosts(); + + // then + assertThat(result).hasSize(2); + assertThat(result).containsExactlyElementsOf(posts); + verify(postRepository).findAll(); + } + + @Test + @DisplayName("게시글 상세 조회 시 조회수 증가") + void getPostDetailWithViewIncrement_success() { + // given + Member member = MemberFixture.create(1L, "user@test.com", "User", "password", Member.Role.MENTEE); + Post post = createPost("제목", "내용", member, Post.PostType.INFORMATIONPOST); + Long postId = 1L; + int initialViewCount = post.getViewCount(); + + when(postRepository.findByIdWithMember(postId)).thenReturn(Optional.of(post)); + + // when + Post result = postService.getPostDetailWithViewIncrement(postId); + + // then + assertThat(result.getViewCount()).isEqualTo(initialViewCount + 1); + verify(postRepository).findByIdWithMember(postId); + } + + @Test + @DisplayName("존재하지 않는 게시글 상세 조회 시 실패") + void getPostDetailWithViewIncrement_notExists_failure() { + // given + Long postId = 999L; + + when(postRepository.findByIdWithMember(postId)).thenReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> postService.getPostDetailWithViewIncrement(postId)) + .isInstanceOf(ServiceException.class) + .hasMessage("400 : 해당 Id의 게시글이 없습니다."); + } + + @Test + @DisplayName("페이징으로 게시글 검색 성공") + void getPosts_withPaging_success() { + // given + String keyword = "테스트"; + int page = 0; + int size = 10; + Post.PostType postType = Post.PostType.INFORMATIONPOST; + Pageable pageable = PageRequest.of(page, size); + + Member member = MemberFixture.create(1L, "user@test.com", "User", "password", Member.Role.MENTEE); + Post post = createPost("테스트 제목", "테스트 내용", member, Post.PostType.INFORMATIONPOST); + List posts = Arrays.asList(post); + Page postPage = new PageImpl<>(posts, pageable, 1); + + when(postRepository.searchPosts(keyword, pageable, postType)).thenReturn(postPage); + + // when + Page result = postService.getPosts(keyword, page, size, postType); + + // then + assertThat(result.getContent()).hasSize(1); + assertThat(result.getTotalElements()).isEqualTo(1); + verify(postRepository).searchPosts(keyword, pageable, postType); + } + + @Test + @DisplayName("ID로 게시글 찾기 성공") + void findById_success() { + // given + Member member = MemberFixture.create(1L, "user@test.com", "User", "password", Member.Role.MENTEE); + Post post = createPost("제목", "내용", member, Post.PostType.INFORMATIONPOST); + Long postId = 1L; + + when(postRepository.findById(postId)).thenReturn(Optional.of(post)); + + // when + Post result = postService.findById(postId); + + // then + assertThat(result).isEqualTo(post); + verify(postRepository).findById(postId); + } + + @Test + @DisplayName("존재하지 않는 ID로 게시글 찾기 실패") + void findById_notExists_failure() { + // given + Long postId = 999L; + + when(postRepository.findById(postId)).thenReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> postService.findById(postId)) + .isInstanceOf(ServiceException.class) + .hasMessage("400 : 해당 Id의 게시글이 없습니다."); + } + } + + private Post createPost(String title, String content, Member member, Post.PostType postType) { + return Post.builder() + .title(title) + .content(content) + .member(member) + .postType(postType) + .build(); + } +}