Add comment query
게시글 댓글 조회 추가 테스트 추가
This commit is contained in:
@@ -1,8 +1,17 @@
|
|||||||
package com.yam.app.comment.application;
|
package com.yam.app.comment.application;
|
||||||
|
|
||||||
|
import com.yam.app.article.domain.ArticleNotFoundException;
|
||||||
|
import com.yam.app.article.domain.ArticleReader;
|
||||||
|
import com.yam.app.comment.domain.Comment;
|
||||||
import com.yam.app.comment.domain.CommentProcessor;
|
import com.yam.app.comment.domain.CommentProcessor;
|
||||||
|
import com.yam.app.comment.domain.CommentReader;
|
||||||
|
import com.yam.app.comment.presentation.CommentResponse;
|
||||||
import com.yam.app.comment.presentation.CreateCommentCommand;
|
import com.yam.app.comment.presentation.CreateCommentCommand;
|
||||||
import com.yam.app.comment.presentation.UpdateCommentCommand;
|
import com.yam.app.comment.presentation.UpdateCommentCommand;
|
||||||
|
import com.yam.app.member.domain.MemberReader;
|
||||||
|
import com.yam.app.member.presentation.MemberResponse;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
@@ -10,9 +19,17 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
public class CommentFacade {
|
public class CommentFacade {
|
||||||
|
|
||||||
private final CommentProcessor commentProcessor;
|
private final CommentProcessor commentProcessor;
|
||||||
|
private final ArticleReader articleReader;
|
||||||
|
private final CommentReader commentReader;
|
||||||
|
private final MemberReader memberReader;
|
||||||
|
|
||||||
public CommentFacade(CommentProcessor commentProcessor) {
|
public CommentFacade(CommentProcessor commentProcessor,
|
||||||
|
ArticleReader articleReader, CommentReader commentReader,
|
||||||
|
MemberReader memberReader) {
|
||||||
this.commentProcessor = commentProcessor;
|
this.commentProcessor = commentProcessor;
|
||||||
|
this.articleReader = articleReader;
|
||||||
|
this.commentReader = commentReader;
|
||||||
|
this.memberReader = memberReader;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@@ -29,4 +46,35 @@ public class CommentFacade {
|
|||||||
public void delete(Long commentId, Long memberId) {
|
public void delete(Long commentId, Long memberId) {
|
||||||
commentProcessor.delete(commentId, memberId);
|
commentProcessor.delete(commentId, memberId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public List<CommentResponse> findByArticleId(Long articleId) {
|
||||||
|
if (!articleReader.existsById(articleId)) {
|
||||||
|
throw new ArticleNotFoundException(articleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return commentReader.findByArticleId(articleId)
|
||||||
|
.stream()
|
||||||
|
.filter(Comment::isAlive)
|
||||||
|
.map(comment -> {
|
||||||
|
var builder = CommentResponse.builder();
|
||||||
|
builder.id(comment.getId());
|
||||||
|
builder.articleId(comment.getArticleId());
|
||||||
|
builder.content(comment.getContent());
|
||||||
|
builder.createAt(comment.getCreatedAt());
|
||||||
|
builder.modifiedAt(comment.getModifiedAt());
|
||||||
|
|
||||||
|
var member = memberReader.findById(comment.getMemberId())
|
||||||
|
.orElseThrow(IllegalStateException::new);
|
||||||
|
|
||||||
|
builder.member(MemberResponse.builder()
|
||||||
|
.id(member.getId())
|
||||||
|
.image(member.getImage())
|
||||||
|
.nickname(member.getNickname())
|
||||||
|
.build());
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.yam.app.comment.presentation;
|
||||||
|
|
||||||
|
import com.yam.app.comment.application.CommentFacade;
|
||||||
|
import com.yam.app.common.ApiResult;
|
||||||
|
import java.util.List;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(
|
||||||
|
produces = MediaType.APPLICATION_JSON_VALUE,
|
||||||
|
consumes = MediaType.APPLICATION_JSON_VALUE
|
||||||
|
)
|
||||||
|
public final class CommentQueryApi {
|
||||||
|
|
||||||
|
private final CommentFacade commentFacade;
|
||||||
|
|
||||||
|
public CommentQueryApi(CommentFacade commentFacade) {
|
||||||
|
this.commentFacade = commentFacade;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/api/comments/{articleId}")
|
||||||
|
public ResponseEntity<ApiResult<List<CommentResponse>>> getComments(
|
||||||
|
@PathVariable Long articleId) {
|
||||||
|
return ResponseEntity.ok(
|
||||||
|
ApiResult.success(commentFacade.findByArticleId(articleId)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.yam.app.comment.presentation;
|
||||||
|
|
||||||
|
import com.yam.app.member.presentation.MemberResponse;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
public final class CommentResponse {
|
||||||
|
|
||||||
|
private final Long id;
|
||||||
|
private final String content;
|
||||||
|
private final LocalDateTime createAt;
|
||||||
|
private final LocalDateTime modifiedAt;
|
||||||
|
private final Long articleId;
|
||||||
|
private final MemberResponse member;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.yam.app.member.presentation;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
public final class MemberResponse {
|
||||||
|
|
||||||
|
private final Long id;
|
||||||
|
private final String nickname;
|
||||||
|
private final String image;
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@ import org.springframework.http.MediaType;
|
|||||||
import org.springframework.test.context.ActiveProfiles;
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
import org.springframework.test.web.servlet.MockMvc;
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
|
||||||
@DisplayName("Article Qurey HTTP API")
|
@DisplayName("Article Query HTTP API")
|
||||||
@WebMvcTest(ArticleQueryApi.class)
|
@WebMvcTest(ArticleQueryApi.class)
|
||||||
@ActiveProfiles("test")
|
@ActiveProfiles("test")
|
||||||
class ArticleQueryApiTest {
|
class ArticleQueryApiTest {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ public final class CommentApiUri {
|
|||||||
public static final String CREATE_COMMENT = "/api/comments/";
|
public static final String CREATE_COMMENT = "/api/comments/";
|
||||||
public static final String UPDATE_COMMENT = "/api/comments/";
|
public static final String UPDATE_COMMENT = "/api/comments/";
|
||||||
public static final String DELETE_COMMENT = "/api/comments/";
|
public static final String DELETE_COMMENT = "/api/comments/";
|
||||||
|
public static final String FIND_BY_ARTICLE_ID = "/api/comments/";
|
||||||
|
|
||||||
private CommentApiUri() {
|
private CommentApiUri() {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.yam.app.comment.presentation;
|
||||||
|
|
||||||
|
import static com.yam.app.comment.presentation.CommentApiUri.FIND_BY_ARTICLE_ID;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.yam.app.comment.application.CommentFacade;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||||
|
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
|
||||||
|
@DisplayName("Comment Query HTTP API")
|
||||||
|
@WebMvcTest(CommentQueryApi.class)
|
||||||
|
@ActiveProfiles("test")
|
||||||
|
class CommentQueryApiTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private MockMvc mockMvc;
|
||||||
|
@Autowired
|
||||||
|
private ObjectMapper objectMapper;
|
||||||
|
@MockBean
|
||||||
|
private CommentFacade commentFacade;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("인증되지 않은 사용자가 댓글 조회 요청을 보냈다면 401에러를 반환한다.")
|
||||||
|
void unauthenticated_user_request() throws Exception {
|
||||||
|
//Act
|
||||||
|
final var actions = mockMvc.perform(get(FIND_BY_ARTICLE_ID + 1)
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.accept(MediaType.APPLICATION_JSON));
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
actions
|
||||||
|
.andExpect(status().isUnauthorized())
|
||||||
|
.andExpect(jsonPath("$.success").value(false))
|
||||||
|
.andExpect(jsonPath("$.data").doesNotExist())
|
||||||
|
.andExpect(jsonPath("$.message").value("Unauthorized request"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -2,10 +2,10 @@ package com.yam.app.integration;
|
|||||||
|
|
||||||
import static com.yam.app.account.presentation.AccountApiUri.LOGIN;
|
import static com.yam.app.account.presentation.AccountApiUri.LOGIN;
|
||||||
import static com.yam.app.article.presentation.ArticleApiUri.FIND_ALL;
|
import static com.yam.app.article.presentation.ArticleApiUri.FIND_ALL;
|
||||||
|
import static com.yam.app.article.presentation.ArticleApiUri.FIND_BY_ID;
|
||||||
import static com.yam.app.article.presentation.ArticleApiUri.WRITE_ARTICLE;
|
import static com.yam.app.article.presentation.ArticleApiUri.WRITE_ARTICLE;
|
||||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
|
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
@@ -64,10 +64,60 @@ final class ArticleIntegrationTests extends AbstractIntegrationTests {
|
|||||||
void default_main_page_find_all_preview_article_response() throws Exception {
|
void default_main_page_find_all_preview_article_response() throws Exception {
|
||||||
// Act
|
// Act
|
||||||
mockMvc.perform(get(FIND_ALL)
|
mockMvc.perform(get(FIND_ALL)
|
||||||
.accept(MediaType.APPLICATION_JSON)
|
.accept(MediaType.APPLICATION_JSON)
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
)
|
)
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(jsonPath("$.data").isArray());
|
.andExpect(jsonPath("$.data").isArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@AutoSource
|
||||||
|
@DisplayName("로그인에 적절한 파라미터를 입력하여, 성공하고 "
|
||||||
|
+ "인증된 사용자가 존재하는 게시글, 존재하지 않는 게시글을 각각 조회하는 시나리오 테스트.")
|
||||||
|
void login_success_and_get_article_by_id() throws Exception {
|
||||||
|
//Arrange
|
||||||
|
var loginCommand = new LoginAccountCommand();
|
||||||
|
loginCommand.setEmail("loginCheck@gmail.com");
|
||||||
|
loginCommand.setPassword("password!");
|
||||||
|
|
||||||
|
var articleId = 1L;
|
||||||
|
var invalidArticleId = 9999L;
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
mockMvc.perform(post(LOGIN)
|
||||||
|
.accept(MediaType.APPLICATION_JSON)
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.content(objectMapper.writeValueAsString(loginCommand))
|
||||||
|
)
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andDo(
|
||||||
|
result -> {
|
||||||
|
final var actions = mockMvc.perform(get(FIND_BY_ID + articleId)
|
||||||
|
.accept(MediaType.APPLICATION_JSON)
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
);
|
||||||
|
|
||||||
|
actions
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.id").isNumber())
|
||||||
|
.andExpect(jsonPath("$.authorId").isNumber())
|
||||||
|
.andExpect(jsonPath("$.title").isString())
|
||||||
|
.andExpect(jsonPath("$.content").isString())
|
||||||
|
.andExpect(jsonPath("$.image").isString())
|
||||||
|
.andExpect(jsonPath("$.tags").isArray());
|
||||||
|
})
|
||||||
|
.andDo(
|
||||||
|
result -> {
|
||||||
|
final var actions = mockMvc.perform(get(FIND_BY_ID + invalidArticleId)
|
||||||
|
.accept(MediaType.APPLICATION_JSON)
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
);
|
||||||
|
|
||||||
|
actions
|
||||||
|
.andExpect(status().isNotFound())
|
||||||
|
.andExpect(jsonPath("$.success").value(false))
|
||||||
|
.andExpect(jsonPath("$.data").doesNotExist());
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,14 @@ package com.yam.app.integration;
|
|||||||
import static com.yam.app.account.presentation.AccountApiUri.LOGIN;
|
import static com.yam.app.account.presentation.AccountApiUri.LOGIN;
|
||||||
import static com.yam.app.comment.presentation.CommentApiUri.CREATE_COMMENT;
|
import static com.yam.app.comment.presentation.CommentApiUri.CREATE_COMMENT;
|
||||||
import static com.yam.app.comment.presentation.CommentApiUri.DELETE_COMMENT;
|
import static com.yam.app.comment.presentation.CommentApiUri.DELETE_COMMENT;
|
||||||
|
import static com.yam.app.comment.presentation.CommentApiUri.FIND_BY_ARTICLE_ID;
|
||||||
import static com.yam.app.comment.presentation.CommentApiUri.UPDATE_COMMENT;
|
import static com.yam.app.comment.presentation.CommentApiUri.UPDATE_COMMENT;
|
||||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
|
||||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
|
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
import com.yam.app.account.presentation.LoginAccountCommand;
|
import com.yam.app.account.presentation.LoginAccountCommand;
|
||||||
@@ -124,4 +127,49 @@ final class CommentIntegrationTests extends AbstractIntegrationTests {
|
|||||||
.andExpect(status().isOk());
|
.andExpect(status().isOk());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("로그인에 적절한 파라미터를 입력하여 성공하고"
|
||||||
|
+ " 인증된 사용자가 유효한 게시글, 유효하지 않는 게시글의 댓글을 각각 조회하는 시나리오 테스트")
|
||||||
|
void login_success_and_get_comment_by_article_id_scenarios() throws Exception {
|
||||||
|
//Arrange
|
||||||
|
var loginCommand = new LoginAccountCommand();
|
||||||
|
loginCommand.setEmail("comment@gmail.com");
|
||||||
|
loginCommand.setPassword("password!");
|
||||||
|
|
||||||
|
var articleId = 1L;
|
||||||
|
var invalidArticleId = 9999L;
|
||||||
|
|
||||||
|
//Act & Assert
|
||||||
|
mockMvc.perform(post(LOGIN)
|
||||||
|
.accept(MediaType.APPLICATION_JSON)
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.content(objectMapper.writeValueAsString(loginCommand))
|
||||||
|
)
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andDo(
|
||||||
|
result -> {
|
||||||
|
final var actions = mockMvc.perform(get(FIND_BY_ARTICLE_ID + articleId)
|
||||||
|
.accept(MediaType.APPLICATION_JSON)
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
);
|
||||||
|
|
||||||
|
actions
|
||||||
|
.andDo(print())
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.data").isArray());
|
||||||
|
})
|
||||||
|
.andDo(
|
||||||
|
result -> {
|
||||||
|
final var actions = mockMvc.perform(get(FIND_BY_ARTICLE_ID + invalidArticleId)
|
||||||
|
.accept(MediaType.APPLICATION_JSON)
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
);
|
||||||
|
|
||||||
|
actions
|
||||||
|
.andDo(print())
|
||||||
|
.andExpect(status().isNotFound())
|
||||||
|
.andExpect(jsonPath("$.data").doesNotExist());
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user