Skip to content

Commit

Permalink
Merge pull request #17 from RossKWSang/feature/findAllAnswer
Browse files Browse the repository at this point in the history
[feat] 단일 질문에 대한 복수 답변 조회 기능
  • Loading branch information
RossKWSang authored Dec 26, 2023
2 parents a056ee3 + f734f74 commit 6df1ef9
Show file tree
Hide file tree
Showing 10 changed files with 407 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.kernel360.kernelsquare.domain.answer.controller;

import com.kernel360.kernelsquare.domain.answer.dto.FindAnswerResponse;
import com.kernel360.kernelsquare.domain.answer.service.AnswerService;
import com.kernel360.kernelsquare.global.common_response.ApiResponse;
import com.kernel360.kernelsquare.global.common_response.ResponseEntityFactory;
import lombok.RequiredArgsConstructor;
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;

import java.util.List;

import static com.kernel360.kernelsquare.global.common_response.response.code.AnswerResponseCode.ANSWERS_ALL_FOUND;

@RestController
@RequestMapping("/api/v1")
@RequiredArgsConstructor
public class AnswerController {

private final AnswerService answerService;

@GetMapping("/questions/{questionId}/answers")
public ResponseEntity<ApiResponse<List<FindAnswerResponse>>> findAllAnswers(@PathVariable Long questionId) {
List<FindAnswerResponse> findAnswerResponses = answerService.findAllAnswer(questionId);
return ResponseEntityFactory.toResponseEntity(ANSWERS_ALL_FOUND, findAnswerResponses);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.kernel360.kernelsquare.domain.answer.dto;

import com.kernel360.kernelsquare.domain.answer.entity.Answer;
import lombok.Builder;

import java.time.LocalDate;

@Builder
public record FindAnswerResponse(
Long id,
Long questionId,
String content,
String rankImageUrl,
String memberImageUrl,
String createdBy,
String answerImageUrl,
String createdDate,
Long voteCount
) {
public static FindAnswerResponse from(Answer answer) {
return new FindAnswerResponse(
answer.getId(),
answer.getQuestion().getId(),
answer.getContent(),
"rankUrl",
answer.getMember().getImageUrl(),
answer.getMember().getNickname(),
answer.getImageUrl(),
answer.getCreatedDate().toLocalDate().toString(),
answer.getVoteCount()
);
}
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
package com.kernel360.kernelsquare.domain.answer.entity;

import com.kernel360.kernelsquare.domain.member.entity.Member;
import com.kernel360.kernelsquare.domain.member_answer_vote.entity.MemberAnswerVote;
import com.kernel360.kernelsquare.domain.question.entity.Question;
import com.kernel360.kernelsquare.domain.rank.entity.Rank;
import com.kernel360.kernelsquare.global.entity.BaseEntity;

import jakarta.persistence.Column;
import jakarta.persistence.ConstraintMode;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.DynamicUpdate;

import java.util.ArrayList;
import java.util.List;

@Entity(name = "answer")
@Getter
@DynamicUpdate
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Answer extends BaseEntity {
@Id
Expand All @@ -41,4 +39,22 @@ public class Answer extends BaseEntity {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "question_id", columnDefinition = "bigint", foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT))
private Question question;

@OneToMany(mappedBy = "answer")
private List<MemberAnswerVote> memberAnswerVote = new ArrayList<>();

@Column(name = "content", columnDefinition = "text")
private String content;

@Column(name = "vote_count", columnDefinition = "smallint")
private Long voteCount;

@Builder
private Answer(String imageUrl, String content, Long voteCount, Member member, Question question) {
this.content = content;
this.voteCount = voteCount;
this.imageUrl = imageUrl;
this.member = member;
this.question = question;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.kernel360.kernelsquare.domain.answer.repository;

import com.kernel360.kernelsquare.domain.answer.entity.Answer;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.List;

public interface AnswerRepository extends JpaRepository<Answer, Long> {
@Query("SELECT a FROM answer a WHERE a.question.id = :questionId ORDER BY a.createdDate DESC")
List<Answer> findAnswersByQuestionIdSortedByCreationDate(@Param("questionId") Long questionId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.kernel360.kernelsquare.domain.answer.service;

import com.kernel360.kernelsquare.domain.answer.dto.FindAnswerResponse;
import com.kernel360.kernelsquare.domain.answer.repository.AnswerRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class AnswerService {
private final AnswerRepository answerRepository;

@Transactional(readOnly = true)
public List<FindAnswerResponse> findAllAnswer(Long questionId) {
return answerRepository.findAnswersByQuestionIdSortedByCreationDate(questionId)
.stream()
.map(FindAnswerResponse::from)
.toList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.kernel360.kernelsquare.global.common_response.response.code;

import com.kernel360.kernelsquare.global.common_response.service.code.AnswerServiceStatus;
import com.kernel360.kernelsquare.global.common_response.service.code.ServiceStatus;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;

@RequiredArgsConstructor
public enum AnswerResponseCode implements ResponseCode {
ANSWER_CREATION_NOT_AUTHORIZED(HttpStatus.FORBIDDEN,
AnswerServiceStatus.ANSWER_CREATION_NOT_AUTHORIZED, "답변을 입력할 권한이 없습니다."),
ANSWER_UPDATE_NOT_AUTHORIZED(HttpStatus.FORBIDDEN,
AnswerServiceStatus.ANSWER_UPDATE_NOT_AUTHORIZED, "답변을 수정할 권한이 없습니다."),

ANSWER_CREATED(HttpStatus.CREATED, AnswerServiceStatus.ANSWER_CREATED, "답변 생성 성공"),
ANSWERS_ALL_FOUND(HttpStatus.OK, AnswerServiceStatus.ANSWERS_ALL_FOUND, "질문에 대한 모든 답변 조회 성공"),
ANSWER_UPDATED(HttpStatus.OK, AnswerServiceStatus.ANSWER_UPDATED, "답변 수정 성공"),
ANSWER_DELETED(HttpStatus.OK, AnswerServiceStatus.ANSWER_DELETED, "답변 삭제 성공"),
VOTE_CREATED(HttpStatus.CREATED, AnswerServiceStatus.VOTE_CREATED, "투표 생성"),
VOTE_DELETED(HttpStatus.OK, AnswerServiceStatus.VOTE_DELETED, "투표 삭제");

private final HttpStatus code;
private final ServiceStatus serviceStatus;
private final String msg;

@Override
public HttpStatus getStatus() {
return code;
}

@Override
public Integer getCode() {
return serviceStatus.getServiceStatus();
}

@Override
public String getMsg() {
return msg;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

@RequiredArgsConstructor
public enum QuestionResponseCode implements ResponseCode {
QUESTION_CREATED(HttpStatus.OK, QuestionServiceStatus.QUESTION_CREATED,"질문 생성 성공"),
QUESTION_CREATED(HttpStatus.CREATED, QuestionServiceStatus.QUESTION_CREATED,"질문 생성 성공"),
QUESTION_FOUND(HttpStatus.OK, QuestionServiceStatus.QUESTION_FOUND,"질문 조회 성공"),
QUESTION_ALL_FOUND(HttpStatus.OK,QuestionServiceStatus.QUESTION_ALL_FOUND, "모든 질문 조회 성공"),
QUESTION_UPDATED(HttpStatus.OK,QuestionServiceStatus.QUESTION_UPDATED , "질문 수정 성공"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.kernel360.kernelsquare.global.common_response.service.code;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public enum AnswerServiceStatus implements ServiceStatus{
ANSWER_CREATION_NOT_AUTHORIZED(2120),
ANSWER_UPDATE_NOT_AUTHORIZED(2121),

ANSWER_CREATED(2150),
ANSWERS_ALL_FOUND(2151),
ANSWER_UPDATED(2152),
ANSWER_DELETED(2153),
VOTE_CREATED(2154),
VOTE_DELETED(2155);

private final Integer code;

@Override
public Integer getServiceStatus() {
return code;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package com.kernel360.kernelsquare.domain.answer.controller;

import com.kernel360.kernelsquare.domain.answer.dto.FindAnswerResponse;
import com.kernel360.kernelsquare.domain.answer.entity.Answer;
import com.kernel360.kernelsquare.domain.answer.service.AnswerService;
import com.kernel360.kernelsquare.domain.member.entity.Member;
import com.kernel360.kernelsquare.domain.question.entity.Question;
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.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;

import static com.kernel360.kernelsquare.global.common_response.response.code.AnswerResponseCode.ANSWERS_ALL_FOUND;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.*;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;

@DisplayName("답변 컨트롤러 단위 테스트")
@WebMvcTest(AnswerController.class)
public class AnswerControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private AnswerService answerService;
private final Long testQuestionId = 1L;
private final Question testQuestion = Question
.builder()
.title("Test Question")
.content("Test Content")
.imageUrl("S3:TestImage")
.closedStatus(false)
.build();

private final Member testMember = Member
.builder()
.nickname("hongjugwang")
.email("[email protected]")
.password("hashedPassword")
.experience(10000L)
.introduction("hi, i'm hongjugwang.")
.imageUrl("s3:qwe12fasdawczx")
.build();

private final Answer testAnswer = Answer
.builder()
.content("Test Answer Content")
.voteCount(10L)
.imageUrl("s3:AnswerImageURL")
.member(testMember)
.question(testQuestion)
.build();

private final FindAnswerResponse findAnswerResponse = FindAnswerResponse
.builder()
.id(1L)
.questionId(1L)
.content(testAnswer.getContent())
.rankImageUrl("s3:RankURL")
.createdBy("HongJuGwang")
.answerImageUrl(testAnswer.getImageUrl())
.memberImageUrl(testMember.getImageUrl())
.createdDate(LocalDate.now().toString())
.voteCount(testAnswer.getVoteCount())
.build();

private List<FindAnswerResponse> answerResponseList = new ArrayList<>();

@Test
@WithMockUser
@DisplayName("답변 조회 성공시, 200 OK, 메시지, 답변정보를 반환한다.")
void testFindAllAnswers() throws Exception {
//given
answerResponseList.add(findAnswerResponse);
doReturn(answerResponseList)
.when(answerService)
.findAllAnswer(anyLong());

//when & then
mockMvc.perform(get("/api/v1/questions/" + testQuestionId + "/answers")
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.characterEncoding("UTF-8"))
.andExpect(status().is(ANSWERS_ALL_FOUND.getStatus().value()))
.andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.code").value(ANSWERS_ALL_FOUND.getCode()))
.andExpect(jsonPath("$.msg").value(ANSWERS_ALL_FOUND.getMsg()))
.andExpect(jsonPath("$.data[0].content").value(testAnswer.getContent()))
.andExpect(jsonPath("$.data[0].answer_image_url").value(testAnswer.getImageUrl()))
.andExpect(jsonPath("$.data[0].vote_count").value(testAnswer.getVoteCount()));

//verify
verify(answerService, times(1)).findAllAnswer(anyLong());
}
}
Loading

0 comments on commit 6df1ef9

Please sign in to comment.