Add comment query
게시글 댓글 조회 추가 테스트 추가
This commit is contained in:
@@ -1,8 +1,17 @@
|
||||
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.CommentReader;
|
||||
import com.yam.app.comment.presentation.CommentResponse;
|
||||
import com.yam.app.comment.presentation.CreateCommentCommand;
|
||||
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.transaction.annotation.Transactional;
|
||||
|
||||
@@ -10,9 +19,17 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
public class CommentFacade {
|
||||
|
||||
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.articleReader = articleReader;
|
||||
this.commentReader = commentReader;
|
||||
this.memberReader = memberReader;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -29,4 +46,35 @@ public class CommentFacade {
|
||||
public void delete(Long commentId, Long 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.web.servlet.MockMvc;
|
||||
|
||||
@DisplayName("Article Qurey HTTP API")
|
||||
@DisplayName("Article Query HTTP API")
|
||||
@WebMvcTest(ArticleQueryApi.class)
|
||||
@ActiveProfiles("test")
|
||||
class ArticleQueryApiTest {
|
||||
|
||||
@@ -5,6 +5,7 @@ public final class CommentApiUri {
|
||||
public static final String CREATE_COMMENT = "/api/comments/";
|
||||
public static final String UPDATE_COMMENT = "/api/comments/";
|
||||
public static final String DELETE_COMMENT = "/api/comments/";
|
||||
public static final String FIND_BY_ARTICLE_ID = "/api/comments/";
|
||||
|
||||
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.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 org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
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 {
|
||||
// Act
|
||||
mockMvc.perform(get(FIND_ALL)
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
)
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
)
|
||||
.andExpect(status().isOk())
|
||||
.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.comment.presentation.CommentApiUri.CREATE_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 org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import com.yam.app.account.presentation.LoginAccountCommand;
|
||||
@@ -124,4 +127,49 @@ final class CommentIntegrationTests extends AbstractIntegrationTests {
|
||||
.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