diff --git a/build.gradle b/build.gradle index bbde3916..c69c530e 100644 --- a/build.gradle +++ b/build.gradle @@ -57,6 +57,8 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' //Junit testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.9.0' + testImplementation 'org.mockito:mockito-inline:4.6.1' + } tasks.named('test') { diff --git a/src/main/java/com/codelion/animalcare/domain/doctorqna/controller/AnswerController.java b/src/main/java/com/codelion/animalcare/domain/doctorqna/controller/AnswerController.java index e686bd97..1eb954f2 100644 --- a/src/main/java/com/codelion/animalcare/domain/doctorqna/controller/AnswerController.java +++ b/src/main/java/com/codelion/animalcare/domain/doctorqna/controller/AnswerController.java @@ -3,10 +3,10 @@ import com.codelion.animalcare.domain.doctorqna.dto.request.AnswerSaveRequestDto; import com.codelion.animalcare.domain.doctorqna.dto.request.AnswerUpdateRequestDto; -import com.codelion.animalcare.domain.doctorqna.service.AnswerQueryService; +import com.codelion.animalcare.domain.doctorqna.entity.Question; import com.codelion.animalcare.domain.doctorqna.service.AnswerCommandService; +import com.codelion.animalcare.domain.doctorqna.service.AnswerQueryService; import com.codelion.animalcare.domain.doctorqna.service.QuestionQueryService; -import com.codelion.animalcare.domain.doctorqna.service.QuestionCommandService; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Controller; @@ -30,9 +30,10 @@ public class AnswerController { //답변 작성 @PostMapping("/usr/doctor-qna/{questionId}/answers/write") - public String save(Model model, @PathVariable Long questionId, @Valid AnswerSaveRequestDto answerSaveRequestDto, BindingResult bindingResult, Principal principal){ + public String save(Model model, @PathVariable Long questionId, + @Valid AnswerSaveRequestDto answerForm, BindingResult bindingResult, Principal principal) { - if(bindingResult.hasErrors()) { + if (bindingResult.hasErrors()) { model.addAttribute("question", questionQueryService.findById(questionId)); return "doctorqna/doctorQnaDetail"; } @@ -40,15 +41,19 @@ public String save(Model model, @PathVariable Long questionId, @Valid AnswerSave // if(!answerService.isDoctor(principal)) { // throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "의사만 답변을 작성할 수 있습니다."); // } + Question question = questionQueryService.findQuestionByQuestionId(questionId); + AnswerSaveRequestDto answerSaveRequestDto = new AnswerSaveRequestDto(answerForm.content(), + question); answerCommandService.save(questionId, answerSaveRequestDto, principal); return "redirect:/usr/doctor-qna/%d".formatted(questionId); } @GetMapping("/usr/doctor-qna/{questionId}/answers/{answerId}/modify") - public String modify(Model model, @PathVariable Long questionId, @PathVariable Long answerId, AnswerUpdateRequestDto answerUpdateRequestDto, Principal principal){ + public String modify(Model model, @PathVariable Long questionId, @PathVariable Long answerId, + AnswerUpdateRequestDto answerUpdateRequestDto, Principal principal) { - if(answerQueryService.answerAuthorized(answerId, principal)){ + if (answerQueryService.answerAuthorized(answerId, principal)) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "수정권한이 없습니다."); } @@ -58,13 +63,15 @@ public String modify(Model model, @PathVariable Long questionId, @PathVariable L } @PostMapping("/usr/doctor-qna/{questionId}/answers/{answerId}/modify") - public String modify(@PathVariable Long questionId, @PathVariable Long answerId, @Valid AnswerUpdateRequestDto answerUpdateRequestDto, BindingResult bindingResult, Principal principal){ + public String modify(@PathVariable Long questionId, @PathVariable Long answerId, + @Valid AnswerUpdateRequestDto answerUpdateRequestDto, BindingResult bindingResult, + Principal principal) { - if(bindingResult.hasErrors()) { + if (bindingResult.hasErrors()) { return "doctorqna/doctorQnaAnswerModifyForm"; } - if(answerQueryService.answerAuthorized(answerId, principal)){ + if (answerQueryService.answerAuthorized(answerId, principal)) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "수정권한이 없습니다."); } @@ -74,10 +81,11 @@ public String modify(@PathVariable Long questionId, @PathVariable Long answerId, //답변 삭제 @GetMapping("/usr/doctor-qna/{questionId}/answers/{answerId}/delete") - public String delete(@PathVariable Long questionId, @PathVariable Long answerId, Principal principal) { + public String delete(@PathVariable Long questionId, @PathVariable Long answerId, + Principal principal) { - if(answerQueryService.answerAuthorized(answerId, principal)){ - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "수정권한이 없습니다."); + if (answerQueryService.answerAuthorized(answerId, principal)) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "삭제권한이 없습니다."); } answerCommandService.delete(answerId); diff --git a/src/main/java/com/codelion/animalcare/domain/doctorqna/controller/QuestionController.java b/src/main/java/com/codelion/animalcare/domain/doctorqna/controller/QuestionController.java index f4fc747e..5b80a11f 100644 --- a/src/main/java/com/codelion/animalcare/domain/doctorqna/controller/QuestionController.java +++ b/src/main/java/com/codelion/animalcare/domain/doctorqna/controller/QuestionController.java @@ -4,8 +4,10 @@ import com.codelion.animalcare.domain.doctorqna.dto.request.QuestionSaveRequestDto; import com.codelion.animalcare.domain.doctorqna.dto.request.QuestionUpdateRequestDto; import com.codelion.animalcare.domain.doctorqna.entity.Question; -import com.codelion.animalcare.domain.doctorqna.service.QuestionQueryService; +import com.codelion.animalcare.domain.doctorqna.entity.QuestionHashtag; import com.codelion.animalcare.domain.doctorqna.service.QuestionCommandService; +import com.codelion.animalcare.domain.doctorqna.service.QuestionHashtagService; +import com.codelion.animalcare.domain.doctorqna.service.QuestionQueryService; import com.codelion.animalcare.domain.user.entity.UserInfo; import com.codelion.animalcare.domain.user.service.UserService; import lombok.RequiredArgsConstructor; @@ -25,8 +27,9 @@ import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; import java.security.Principal; +import java.util.List; - +//TODO : 2022/04/06 -> 참조하지 않는 해시태그는? (배치?), 게시물 수정할때 해시태그도 수정 가능하게 하기, 해시태그 누르면 리스트 띄우기, ... @RequiredArgsConstructor @Controller public class QuestionController { @@ -34,21 +37,22 @@ public class QuestionController { private final QuestionCommandService questionCommandService; private final QuestionQueryService questionQueryService; private final UserService userService; + private final QuestionHashtagService questionHashtagService; //게시글 등록 화면 @GetMapping("/usr/doctor-qna/write") - public String saveForm(QuestionSaveRequestDto questionSaveRequestDto){ + public String saveForm(QuestionSaveRequestDto questionSaveRequestDto) { return "doctorqna/doctorQnaQuestionForm"; } //게시글 등록 @PostMapping("/usr/doctor-qna/write") - public String save(@Valid QuestionSaveRequestDto questionSaveRequestDto, BindingResult bindingResult, Principal principal) { + public String save(@Valid QuestionSaveRequestDto questionSaveRequestDto, + BindingResult bindingResult, Principal principal) { - if(bindingResult.hasErrors()) { + if (bindingResult.hasErrors()) { return "doctorqna/doctorQnaQuestionForm"; } - questionCommandService.save(questionSaveRequestDto, principal); return "redirect:/usr/doctor-qna"; @@ -56,20 +60,22 @@ public String save(@Valid QuestionSaveRequestDto questionSaveRequestDto, Binding //개별 조회 @GetMapping("/usr/doctor-qna/{id}") - public String findById(Model model, @PathVariable Long id, AnswerSaveRequestDto answerSaveRequestDto, HttpServletRequest request, HttpServletResponse response, Principal principal){ + public String findById(Model model, @PathVariable Long id, + AnswerSaveRequestDto answerSaveRequestDto, HttpServletRequest request, + HttpServletResponse response, Principal principal) { // 조회수 쿠키로 중복방지 Cookie oldCookie = null; Cookie[] cookies = request.getCookies(); - if(cookies != null) { - for(var cookie : cookies) { - if(cookie.getName().equals("DoctorQnaView")) { + if (cookies != null) { + for (var cookie : cookies) { + if (cookie.getName().equals("DoctorQnaView")) { oldCookie = cookie; } } } - if(oldCookie != null) { - if(!oldCookie.getValue().contains("[" + id.toString() + "]")) { + if (oldCookie != null) { + if (!oldCookie.getValue().contains("[" + id.toString() + "]")) { questionCommandService.updateView(id); oldCookie.setValue(oldCookie.getValue() + "_[" + id + "]"); oldCookie.setPath("/"); @@ -90,7 +96,7 @@ public String findById(Model model, @PathVariable Long id, AnswerSaveRequestDto UserInfo user = userService.getUserInfo(principal.getName()).orElse(null); - if(user != null) { // 로그인 한 사용자라면 + if (user != null) { // 로그인 한 사용자라면 model.addAttribute("login_id", user.getId()); like = questionQueryService.findLike(id, user); // 로그인 유저의 추천 여부 확인 @@ -99,40 +105,54 @@ public String findById(Model model, @PathVariable Long id, AnswerSaveRequestDto model.addAttribute("like", like); + List hashtags = questionHashtagService.findHashtagListByQuestion( + questionQueryService.findQuestionByQuestionId(id)); + model.addAttribute("hashtags", hashtags); return "doctorqna/doctorQnaDetail"; } + //전체 조회 @GetMapping("/usr/doctor-qna") - public String findAll(Model model, @RequestParam(value="page", defaultValue="0") int page, String type, String kw) { + public String findAll(Model model, @RequestParam(value = "page", defaultValue = "0") int page, + String type, String kw, String hashtag) { - Page paging = questionQueryService.findAll(page, type, kw); + Page paging; + + if(hashtag != null && !hashtag.isEmpty()) { + paging = questionHashtagService.findAllByHashtag(page, hashtag); + }else { + paging = questionQueryService.findAll(page, type, kw); + } model.addAttribute("paging", paging); model.addAttribute("kw", kw); model.addAttribute("type", type); - return "doctorqna/doctorQnaList"; } @GetMapping("/usr/doctor-qna/{id}/modify") - public String update(Model model, @PathVariable Long id, QuestionUpdateRequestDto questionUpdateRequestDto, Principal principal){ + public String update(Model model, @PathVariable Long id, + QuestionUpdateRequestDto questionUpdateRequestDto, Principal principal) { - if(questionQueryService.questionAuthorized(id, principal)){ + if (questionQueryService.questionAuthorized(id, principal)) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "수정권한이 없습니다."); } model.addAttribute("question", questionQueryService.findById(id)); return "doctorqna/doctorQnaQuestionModifyForm"; } + @PostMapping("/usr/doctor-qna/{id}/modify") - public String update(@PathVariable Long id, @Valid QuestionUpdateRequestDto questionUpdateRequestDto, BindingResult bindingResult, Principal principal){ + public String update(@PathVariable Long id, + @Valid QuestionUpdateRequestDto questionUpdateRequestDto, BindingResult bindingResult, + Principal principal) { - if(bindingResult.hasErrors()) { + if (bindingResult.hasErrors()) { return "doctorqna/doctorQnaQuestionModifyForm"; } - if(questionQueryService.questionAuthorized(id, principal)){ + if (questionQueryService.questionAuthorized(id, principal)) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "수정권한이 없습니다."); } @@ -142,9 +162,9 @@ public String update(@PathVariable Long id, @Valid QuestionUpdateRequestDto ques } @GetMapping("/usr/doctor-qna/{id}/delete") - public String delete(@PathVariable Long id, Principal principal){ + public String delete(@PathVariable Long id, Principal principal) { - if(questionQueryService.questionAuthorized(id, principal)){ + if (questionQueryService.questionAuthorized(id, principal)) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "삭제권한이 없습니다."); } diff --git a/src/main/java/com/codelion/animalcare/domain/doctorqna/dto/request/AnswerSaveRequestDto.java b/src/main/java/com/codelion/animalcare/domain/doctorqna/dto/request/AnswerSaveRequestDto.java index 39e509f6..cfb45097 100644 --- a/src/main/java/com/codelion/animalcare/domain/doctorqna/dto/request/AnswerSaveRequestDto.java +++ b/src/main/java/com/codelion/animalcare/domain/doctorqna/dto/request/AnswerSaveRequestDto.java @@ -4,33 +4,27 @@ import com.codelion.animalcare.domain.doctorqna.entity.Question; import com.codelion.animalcare.domain.user.entity.Doctor; import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; import javax.validation.constraints.NotEmpty; -@Getter -@Setter -@NoArgsConstructor -public class AnswerSaveRequestDto { - +public record AnswerSaveRequestDto( @NotEmpty(message = "내용은 필수 입력 항목입니다.") - private String content; - @Setter - private Question question; + String content, + + Question question +) { @Builder - public AnswerSaveRequestDto(String content, Question question){ + public AnswerSaveRequestDto(String content, Question question) { this.content = content; this.question = question; } public Answer toEntity(Doctor doctor) { return Answer.builder() - .content(content) - .question(question) - .doctor(doctor) - .build(); + .content(content) + .question(question) + .doctor(doctor) + .build(); } } diff --git a/src/main/java/com/codelion/animalcare/domain/doctorqna/dto/request/AnswerUpdateRequestDto.java b/src/main/java/com/codelion/animalcare/domain/doctorqna/dto/request/AnswerUpdateRequestDto.java index efaef92b..6fb2193c 100644 --- a/src/main/java/com/codelion/animalcare/domain/doctorqna/dto/request/AnswerUpdateRequestDto.java +++ b/src/main/java/com/codelion/animalcare/domain/doctorqna/dto/request/AnswerUpdateRequestDto.java @@ -1,19 +1,13 @@ package com.codelion.animalcare.domain.doctorqna.dto.request; import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; import javax.validation.constraints.NotBlank; -@Getter -@Setter -@NoArgsConstructor -public class AnswerUpdateRequestDto { - +public record AnswerUpdateRequestDto( @NotBlank(message = "내용은 필수 입력 항목입니다.") - private String content; + String content +) { @Builder public AnswerUpdateRequestDto(String content) { diff --git a/src/main/java/com/codelion/animalcare/domain/doctorqna/dto/request/QuestionSaveRequestDto.java b/src/main/java/com/codelion/animalcare/domain/doctorqna/dto/request/QuestionSaveRequestDto.java index 5e8c1ef5..5747aa5f 100644 --- a/src/main/java/com/codelion/animalcare/domain/doctorqna/dto/request/QuestionSaveRequestDto.java +++ b/src/main/java/com/codelion/animalcare/domain/doctorqna/dto/request/QuestionSaveRequestDto.java @@ -5,19 +5,24 @@ import javax.validation.constraints.NotBlank; import javax.validation.constraints.Size; +import java.util.List; public record QuestionSaveRequestDto( - @NotBlank(message = "제목은 필수 입력 사항입니다.") - @Size(max = 200, message = "제목이 너무 길어요.") - String title, + @NotBlank(message = "제목은 필수 입력 사항입니다.") + @Size(max = 200, message = "제목이 너무 길어요.") + String title, - @NotBlank(message = "내용은 필수 입력 사항입니다.") String content + @NotBlank(message = "내용은 필수 입력 사항입니다.") + String content, + + List hashtags ) { + public Question toEntity(Member member) { return Question.builder() - .title(title()) - .content(content()) - .member(member) - .build(); + .title(title()) + .content(content()) + .member(member) + .build(); } } diff --git a/src/main/java/com/codelion/animalcare/domain/doctorqna/dto/request/QuestionUpdateRequestDto.java b/src/main/java/com/codelion/animalcare/domain/doctorqna/dto/request/QuestionUpdateRequestDto.java index e3d9faea..9dab40d9 100644 --- a/src/main/java/com/codelion/animalcare/domain/doctorqna/dto/request/QuestionUpdateRequestDto.java +++ b/src/main/java/com/codelion/animalcare/domain/doctorqna/dto/request/QuestionUpdateRequestDto.java @@ -1,23 +1,18 @@ package com.codelion.animalcare.domain.doctorqna.dto.request; import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; import javax.validation.constraints.NotBlank; import javax.validation.constraints.Size; -@Getter -@Setter -@NoArgsConstructor -public class QuestionUpdateRequestDto { +public record QuestionUpdateRequestDto( @NotBlank(message = "제목은 필수 항목 입니다.") @Size(max = 200) - private String title; + String title, @NotBlank(message = "내용은 필수 항목 입니다.") - private String content; + String content +) { @Builder public QuestionUpdateRequestDto(String title, String content) { diff --git a/src/main/java/com/codelion/animalcare/domain/doctorqna/dto/response/AnswerResponseDto.java b/src/main/java/com/codelion/animalcare/domain/doctorqna/dto/response/AnswerResponseDto.java index abe0511b..a1870a67 100644 --- a/src/main/java/com/codelion/animalcare/domain/doctorqna/dto/response/AnswerResponseDto.java +++ b/src/main/java/com/codelion/animalcare/domain/doctorqna/dto/response/AnswerResponseDto.java @@ -5,16 +5,17 @@ import java.time.LocalDateTime; -public record AnswerResponseDto ( - +public record AnswerResponseDto( Long id, String content, Question question, LocalDateTime createdAt, LocalDateTime updatedAt ) { + public AnswerResponseDto(Answer entity) { - this(entity.getId(), entity.getContent(), entity.getQuestion(), entity.getCreatedAt(), entity.getUpdatedAt()); + this(entity.getId(), entity.getContent(), entity.getQuestion(), entity.getCreatedAt(), + entity.getUpdatedAt()); } } diff --git a/src/main/java/com/codelion/animalcare/domain/doctorqna/dto/response/QuestionListResponseDto.java b/src/main/java/com/codelion/animalcare/domain/doctorqna/dto/response/QuestionListResponseDto.java index 8a247ee5..257ae0c6 100644 --- a/src/main/java/com/codelion/animalcare/domain/doctorqna/dto/response/QuestionListResponseDto.java +++ b/src/main/java/com/codelion/animalcare/domain/doctorqna/dto/response/QuestionListResponseDto.java @@ -5,7 +5,7 @@ import java.time.LocalDateTime; -public record QuestionListResponseDto ( +public record QuestionListResponseDto( Long id, String title, LocalDateTime createdAt, @@ -14,7 +14,9 @@ public record QuestionListResponseDto ( Member member ) { + public QuestionListResponseDto(Question entity) { - this(entity.getId(), entity.getTitle(), entity.getCreatedAt(), entity.getView(), entity.getLikeCount(), entity.getMember()); + this(entity.getId(), entity.getTitle(), entity.getCreatedAt(), entity.getView(), + entity.getLikeCount(), entity.getMember()); } } diff --git a/src/main/java/com/codelion/animalcare/domain/doctorqna/dto/response/QuestionResponseDto.java b/src/main/java/com/codelion/animalcare/domain/doctorqna/dto/response/QuestionResponseDto.java index ff5b2c55..0ecf205d 100644 --- a/src/main/java/com/codelion/animalcare/domain/doctorqna/dto/response/QuestionResponseDto.java +++ b/src/main/java/com/codelion/animalcare/domain/doctorqna/dto/response/QuestionResponseDto.java @@ -7,8 +7,8 @@ import java.time.LocalDateTime; import java.util.List; -public record QuestionResponseDto ( - +//TODO : 여기에, hashtags를 추가해주는 것이 db쿼리를 줄일 수 있을 것 같은데, 그걸 위해 question에 hashtags를 멤버변수로 가지는 것이 괜찮을 지 생각해보기 +public record QuestionResponseDto( Long id, String title, String content, @@ -17,10 +17,13 @@ public record QuestionResponseDto ( List answerList, Member member, int likeCount + ) { - public QuestionResponseDto(Question entity){ - this(entity.getId(), entity.getTitle(), entity.getContent(), entity.getCreatedAt(), entity.getView(), - entity.getAnswerList(), entity.getMember(), entity.getLikeCount()); + + public QuestionResponseDto(Question entity) { + this(entity.getId(), entity.getTitle(), entity.getContent(), entity.getCreatedAt(), + entity.getView(), + entity.getAnswerList(), entity.getMember(), entity.getLikeCount()); } } diff --git a/src/main/java/com/codelion/animalcare/domain/doctorqna/entity/Answer.java b/src/main/java/com/codelion/animalcare/domain/doctorqna/entity/Answer.java index 825bb6cc..4d4c86db 100644 --- a/src/main/java/com/codelion/animalcare/domain/doctorqna/entity/Answer.java +++ b/src/main/java/com/codelion/animalcare/domain/doctorqna/entity/Answer.java @@ -1,20 +1,19 @@ package com.codelion.animalcare.domain.doctorqna.entity; +import static javax.persistence.FetchType.LAZY; + import com.codelion.animalcare.domain.user.entity.Doctor; import com.codelion.animalcare.global.common.entity.BaseEntity; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - import javax.persistence.Column; import javax.persistence.ConstraintMode; import javax.persistence.Entity; import javax.persistence.ForeignKey; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; - -import static javax.persistence.FetchType.*; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; @Getter @NoArgsConstructor @@ -40,7 +39,7 @@ public Answer(String content, Question question, Doctor doctor) { this.doctor = doctor; } - public void update(String content){ + public void update(String content) { this.content = content; } diff --git a/src/main/java/com/codelion/animalcare/domain/doctorqna/entity/Hashtag.java b/src/main/java/com/codelion/animalcare/domain/doctorqna/entity/Hashtag.java new file mode 100644 index 00000000..2b18b018 --- /dev/null +++ b/src/main/java/com/codelion/animalcare/domain/doctorqna/entity/Hashtag.java @@ -0,0 +1,26 @@ +package com.codelion.animalcare.domain.doctorqna.entity; + +import com.codelion.animalcare.global.common.entity.BaseEntity; +import java.util.List; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.OneToMany; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@Entity +public class Hashtag extends BaseEntity { + + private String tagName; + + @OneToMany(mappedBy = "hashtag", cascade = CascadeType.ALL, orphanRemoval = true) + private List questionHashtags; + + @Builder + public Hashtag(String tagName) { + this.tagName = tagName; + } +} diff --git a/src/main/java/com/codelion/animalcare/domain/doctorqna/entity/Question.java b/src/main/java/com/codelion/animalcare/domain/doctorqna/entity/Question.java index 9f32897c..e94a9478 100644 --- a/src/main/java/com/codelion/animalcare/domain/doctorqna/entity/Question.java +++ b/src/main/java/com/codelion/animalcare/domain/doctorqna/entity/Question.java @@ -38,7 +38,11 @@ public class Question extends BaseEntity { private Member member; @OneToMany(mappedBy = "question", cascade = CascadeType.ALL, orphanRemoval = true) - private List QuestionLike; + private List QuestionLike; + + @OneToMany(mappedBy = "question", cascade = CascadeType.ALL, orphanRemoval = true) + private List questionHashtags; + @Builder public Question(String title, String content, int view, int likeCount, Member member) { this.title = title; @@ -48,10 +52,11 @@ public Question(String title, String content, int view, int likeCount, Member me this.likeCount = likeCount; } - public void update(String title, String content){ + public void update(String title, String content) { this.title = title; this.content = content; } + public void addAnswer(Answer answer) { answer.setQuestion(this); //getAnswerList().add(answer); diff --git a/src/main/java/com/codelion/animalcare/domain/doctorqna/entity/QuestionHashtag.java b/src/main/java/com/codelion/animalcare/domain/doctorqna/entity/QuestionHashtag.java new file mode 100644 index 00000000..f4bc4367 --- /dev/null +++ b/src/main/java/com/codelion/animalcare/domain/doctorqna/entity/QuestionHashtag.java @@ -0,0 +1,23 @@ +package com.codelion.animalcare.domain.doctorqna.entity; + +import com.codelion.animalcare.global.common.entity.BaseEntity; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import javax.persistence.Entity; +import javax.persistence.ManyToOne; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Entity +public class QuestionHashtag extends BaseEntity { + + @ManyToOne + private Question question; + + @ManyToOne + private Hashtag hashtag; + +} diff --git a/src/main/java/com/codelion/animalcare/domain/doctorqna/repository/HashtagRepository.java b/src/main/java/com/codelion/animalcare/domain/doctorqna/repository/HashtagRepository.java new file mode 100644 index 00000000..8d03bfa5 --- /dev/null +++ b/src/main/java/com/codelion/animalcare/domain/doctorqna/repository/HashtagRepository.java @@ -0,0 +1,11 @@ +package com.codelion.animalcare.domain.doctorqna.repository; + +import com.codelion.animalcare.domain.doctorqna.entity.Hashtag; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface HashtagRepository extends JpaRepository { + + Optional findByTagName(String tagName); +} diff --git a/src/main/java/com/codelion/animalcare/domain/doctorqna/repository/QuestionHashtagRepository.java b/src/main/java/com/codelion/animalcare/domain/doctorqna/repository/QuestionHashtagRepository.java new file mode 100644 index 00000000..bc55d002 --- /dev/null +++ b/src/main/java/com/codelion/animalcare/domain/doctorqna/repository/QuestionHashtagRepository.java @@ -0,0 +1,14 @@ +package com.codelion.animalcare.domain.doctorqna.repository; + +import com.codelion.animalcare.domain.doctorqna.entity.Question; +import com.codelion.animalcare.domain.doctorqna.entity.QuestionHashtag; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface QuestionHashtagRepository extends JpaRepository { + + List findAllByQuestion(Question question); + + List findAllByHashtagTagName(String tagName); +} diff --git a/src/main/java/com/codelion/animalcare/domain/doctorqna/repository/QuestionRepository.java b/src/main/java/com/codelion/animalcare/domain/doctorqna/repository/QuestionRepository.java index dc72b0a6..81aad07c 100644 --- a/src/main/java/com/codelion/animalcare/domain/doctorqna/repository/QuestionRepository.java +++ b/src/main/java/com/codelion/animalcare/domain/doctorqna/repository/QuestionRepository.java @@ -17,10 +17,11 @@ public interface QuestionRepository extends JpaRepository { Page findByContentContaining(String kw, Pageable pageable); @Query("select q from Question q where q.member.name like concat('%',UPPER(:kw),'%')") - Page findByMemberContaining(@Param("kw")String kw, Pageable pageable); + Page findByMemberContaining(@Param("kw") String kw, Pageable pageable); @Query("select q from Question q where q.title like concat('%', UPPER(:kw), '%') or q.content like concat('%', UPPER(:kw), '%')") - Page findByTitleOrContent(@Param("kw")String kw, Pageable pageable); + Page findByTitleOrContent(@Param("kw") String kw, Pageable pageable); + @Modifying(clearAutomatically = true) @Query("update Question q set q.view = q.view + 1 where q.id = :id") int updateView(@Param("id") Long id); diff --git a/src/main/java/com/codelion/animalcare/domain/doctorqna/service/AnswerCommandService.java b/src/main/java/com/codelion/animalcare/domain/doctorqna/service/AnswerCommandService.java index ad274c34..90e388a1 100644 --- a/src/main/java/com/codelion/animalcare/domain/doctorqna/service/AnswerCommandService.java +++ b/src/main/java/com/codelion/animalcare/domain/doctorqna/service/AnswerCommandService.java @@ -4,7 +4,6 @@ import com.codelion.animalcare.domain.doctorqna.dto.request.AnswerUpdateRequestDto; import com.codelion.animalcare.domain.doctorqna.entity.Answer; import com.codelion.animalcare.domain.doctorqna.repository.AnswerRepository; -import com.codelion.animalcare.domain.doctorqna.entity.Question; import com.codelion.animalcare.domain.user.entity.Doctor; import com.codelion.animalcare.domain.user.repository.DoctorRepository; import lombok.RequiredArgsConstructor; @@ -24,24 +23,24 @@ public class AnswerCommandService { private final QuestionQueryService questionQueryService; - public Long save(Long questionId, AnswerSaveRequestDto answerSaveRequestDto, Principal principal){ - Question question = questionQueryService.findQuestionByQuestionId(questionId); - answerSaveRequestDto.setQuestion(question); + public Long save(Long questionId, AnswerSaveRequestDto answerSaveRequestDto, + Principal principal) { - Doctor doctor = doctorRepository.findByEmail(principal.getName()).orElseThrow(() -> new IllegalArgumentException("의사가 존재하지 않습니다.")); + Doctor doctor = doctorRepository.findByEmail(principal.getName()).orElseThrow( + () -> new IllegalArgumentException("의사가 존재하지 않습니다.")); // if(!member.getAuth().equals("doctor")) { // throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "의사만 답변을 작성할 수 있습니다."); // } - question.addAnswer(answerSaveRequestDto.toEntity(doctor)); + answerSaveRequestDto.question().addAnswer(answerSaveRequestDto.toEntity(doctor)); return answerRepository.save(answerSaveRequestDto.toEntity(doctor)).getId(); } public Long update(Long answerId, AnswerUpdateRequestDto answerUpdateRequestDto) { Answer answer = answerQueryService.findAnswerByAnswerId(answerId); - answer.update(answerUpdateRequestDto.getContent()); + answer.update(answerUpdateRequestDto.content()); return answerId; } diff --git a/src/main/java/com/codelion/animalcare/domain/doctorqna/service/AnswerQueryService.java b/src/main/java/com/codelion/animalcare/domain/doctorqna/service/AnswerQueryService.java index a7b84f80..c2469135 100644 --- a/src/main/java/com/codelion/animalcare/domain/doctorqna/service/AnswerQueryService.java +++ b/src/main/java/com/codelion/animalcare/domain/doctorqna/service/AnswerQueryService.java @@ -19,7 +19,7 @@ public class AnswerQueryService { public boolean answerAuthorized(Long answerId, Principal principal) { Answer answer = findAnswerByAnswerId(answerId); - if(answer.getDoctor().getEmail().equals(principal.getName())) { + if (answer.getDoctor().getEmail().equals(principal.getName())) { return false; } @@ -27,10 +27,11 @@ public boolean answerAuthorized(Long answerId, Principal principal) { } public Answer findAnswerByAnswerId(Long answerId) { - return answerRepository.findById(answerId).orElseThrow(() -> new IllegalArgumentException("답변이 존재하지 않습니다.")); + return answerRepository.findById(answerId) + .orElseThrow(() -> new IllegalArgumentException("답변이 존재하지 않습니다.")); } - public AnswerResponseDto findById(Long answerId){ + public AnswerResponseDto findById(Long answerId) { Answer entity = findAnswerByAnswerId(answerId); return new AnswerResponseDto(entity); diff --git a/src/main/java/com/codelion/animalcare/domain/doctorqna/service/HashtagService.java b/src/main/java/com/codelion/animalcare/domain/doctorqna/service/HashtagService.java new file mode 100644 index 00000000..3e8821b1 --- /dev/null +++ b/src/main/java/com/codelion/animalcare/domain/doctorqna/service/HashtagService.java @@ -0,0 +1,29 @@ +package com.codelion.animalcare.domain.doctorqna.service; + +import com.codelion.animalcare.domain.doctorqna.entity.Hashtag; +import com.codelion.animalcare.domain.doctorqna.repository.HashtagRepository; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Transactional +@Service +public class HashtagService { + + private final HashtagRepository hashtagRepository; + + public Optional findByTagName(String tagName) { + + return hashtagRepository.findByTagName(tagName); + } + + public Hashtag save(String tagName) { + + return hashtagRepository.save( + Hashtag.builder() + .tagName(tagName) + .build()); + } +} diff --git a/src/main/java/com/codelion/animalcare/domain/doctorqna/service/QuestionCommandService.java b/src/main/java/com/codelion/animalcare/domain/doctorqna/service/QuestionCommandService.java index eda1d3f9..1f1cca69 100644 --- a/src/main/java/com/codelion/animalcare/domain/doctorqna/service/QuestionCommandService.java +++ b/src/main/java/com/codelion/animalcare/domain/doctorqna/service/QuestionCommandService.java @@ -21,21 +21,30 @@ public class QuestionCommandService { private final QuestionQueryService questionQueryService; + + private final MemberService memberService; + + private final QuestionHashtagService questionHashtagService; + private final QuestionRepository questionRepository; + private final QuestionLikeRepository questionLikeRepository; - private final MemberService memberService; public Long save(QuestionSaveRequestDto questionSaveRequestDto, Principal principal) { Member member = memberService.findMemberByEmail(principal.getName()); - return questionRepository.save(questionSaveRequestDto.toEntity(member)).getId(); - } + Question savedQuestion = questionRepository.save(questionSaveRequestDto.toEntity(member)); + questionSaveRequestDto.hashtags().forEach(System.out::println); + questionHashtagService.saveHashtag(savedQuestion, questionSaveRequestDto.hashtags()); + return savedQuestion.getId(); + } + public Long update(Long id, QuestionUpdateRequestDto questionUpdateRequestDto){ Question question = questionQueryService.findQuestionByQuestionId(id); - question.update(questionUpdateRequestDto.getTitle(), questionUpdateRequestDto.getContent()); + question.update(questionUpdateRequestDto.title(), questionUpdateRequestDto.content()); return id; } diff --git a/src/main/java/com/codelion/animalcare/domain/doctorqna/service/QuestionHashtagService.java b/src/main/java/com/codelion/animalcare/domain/doctorqna/service/QuestionHashtagService.java new file mode 100644 index 00000000..6d4e0845 --- /dev/null +++ b/src/main/java/com/codelion/animalcare/domain/doctorqna/service/QuestionHashtagService.java @@ -0,0 +1,61 @@ +package com.codelion.animalcare.domain.doctorqna.service; + +import com.codelion.animalcare.domain.doctorqna.entity.Hashtag; +import com.codelion.animalcare.domain.doctorqna.entity.Question; +import com.codelion.animalcare.domain.doctorqna.entity.QuestionHashtag; +import com.codelion.animalcare.domain.doctorqna.repository.QuestionHashtagRepository; +import java.util.ArrayList; +import java.util.List; +import lombok.RequiredArgsConstructor; +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 org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Transactional +@Service +public class QuestionHashtagService { + + private final HashtagService hashtagService; + + private final QuestionHashtagRepository questionHashtagRepository; + + public void saveHashtag(Question question, List tagNames) { + + if(tagNames.size() == 0) return; + + tagNames.stream() + .map(hashtag -> + hashtagService.findByTagName(hashtag) + .orElseGet(() -> hashtagService.save(hashtag))) + .forEach(hashtag -> mapHashtagToQuestion(question, hashtag)); + } + + private Long mapHashtagToQuestion(Question question, Hashtag hashtag) { + + return questionHashtagRepository.save(new QuestionHashtag(question, hashtag)).getId(); + } + + public List findHashtagListByQuestion(Question question) { + + return questionHashtagRepository.findAllByQuestion(question); + } + + public Page findAllByHashtag(int page, String hashtag) { + List sortsList = new ArrayList<>(); + sortsList.add(Sort.Order.desc("createdAt")); + + Pageable pageable = PageRequest.of(page, 10, Sort.by(sortsList)); + + List questionList = questionHashtagRepository.findAllByHashtagTagName(hashtag) + .stream() + .map(QuestionHashtag::getQuestion) + .toList(); + + return new PageImpl<>(questionList, pageable, questionList.size()); + } +} diff --git a/src/main/java/com/codelion/animalcare/domain/doctorqna/service/QuestionQueryService.java b/src/main/java/com/codelion/animalcare/domain/doctorqna/service/QuestionQueryService.java index 1b23aa57..30b7c35a 100644 --- a/src/main/java/com/codelion/animalcare/domain/doctorqna/service/QuestionQueryService.java +++ b/src/main/java/com/codelion/animalcare/domain/doctorqna/service/QuestionQueryService.java @@ -18,6 +18,16 @@ import java.util.ArrayList; import java.util.List; +/* + +TODO : SERVICE단에서 QUESTION ENTITY를 직접 반환하지 않고 DTO를 반환하는 것이 요청에 대한 응답만 함으로써 + 성능, 보안적으로 이점을 얻을 수 있다고 생각한다. 하지만, SERVICE단에서 ENTITY가 필요한 경우에 findQuestionByQuestionId를 통해 직접 ENTITY를 얻고 있는데, + 이는 어찌보면 오버엔지니어링이 아닌가 하는 생각도 있다. 이를 어떻게 해결하는 것이 좋은 방법인지 확신이 서지 않는다. + 이를 개선하기 위해서 떠올린 방법으로는 + 1. 그냥 service단에서 entity를 반환하기 + 2. modelMapper를 사용하기 + + */ @Service @RequiredArgsConstructor @Transactional(readOnly = true) @@ -27,15 +37,15 @@ public class QuestionQueryService { private final QuestionLikeRepository questionLikeRepository; - public QuestionResponseDto findById(Long id){ + public QuestionResponseDto findById(Long id) { Question entity = findQuestionByQuestionId(id); return new QuestionResponseDto(entity); } - public boolean questionAuthorized(Long id, Principal principal){ + public boolean questionAuthorized(Long id, Principal principal) { Question question = findQuestionByQuestionId(id); - if(question.getMember().getEmail().equals(principal.getName())) { + if (question.getMember().getEmail().equals(principal.getName())) { return false; } @@ -47,7 +57,8 @@ public boolean findLike(Long id, UserInfo user) { } public Question findQuestionByQuestionId(Long id) { - return questionRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("질문이 존재하지 않습니다.")); + return questionRepository.findById(id) + .orElseThrow(() -> new IllegalArgumentException("질문이 존재하지 않습니다.")); } public Page findAll(int page, String type, String kw) { @@ -75,4 +86,5 @@ public Page findAll(int page, String type, String kw) { } } + } diff --git a/src/main/resources/templates/doctorqna/doctorQnaDetail.html b/src/main/resources/templates/doctorqna/doctorQnaDetail.html index 01c9fc86..71e632da 100644 --- a/src/main/resources/templates/doctorqna/doctorQnaDetail.html +++ b/src/main/resources/templates/doctorqna/doctorQnaDetail.html @@ -32,7 +32,12 @@

- + + +
+
diff --git a/src/main/resources/templates/doctorqna/doctorQnaQuestionForm.html b/src/main/resources/templates/doctorqna/doctorQnaQuestionForm.html index bfd158a5..4b1940d8 100644 --- a/src/main/resources/templates/doctorqna/doctorQnaQuestionForm.html +++ b/src/main/resources/templates/doctorqna/doctorQnaQuestionForm.html @@ -1,4 +1,7 @@ - +
@@ -15,7 +18,57 @@
질문등록
+
+ +
+ +
+ +
+
- + + + diff --git a/src/test/java/com/codelion/animalcare/doctorqna/controller/QuestionControllerTest.java b/src/test/java/com/codelion/animalcare/doctorqna/controller/QuestionControllerTest.java deleted file mode 100644 index 3245d014..00000000 --- a/src/test/java/com/codelion/animalcare/doctorqna/controller/QuestionControllerTest.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.codelion.animalcare.doctorqna.controller; - -import com.codelion.animalcare.domain.doctorqna.dto.request.QuestionSaveRequestDto; -import com.codelion.animalcare.domain.doctorqna.service.QuestionCommandService; -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.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.http.MediaType; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.web.WebAppConfiguration; -import org.springframework.test.web.servlet.MockMvc; - -import java.security.Principal; - -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@ActiveProfiles("test") -@WebAppConfiguration -@SpringBootTest -@AutoConfigureMockMvc -public class QuestionControllerTest { - - @Autowired - private MockMvc mockMvc; - @MockBean - private QuestionCommandService questionCommandService; - - - @DisplayName("hi") - @Test - public void saveQuestion_ValidInput_ShouldSaveQuestion() throws Exception { - QuestionSaveRequestDto dto = new QuestionSaveRequestDto("Test question", "Test content"); - Principal principal = () -> "member1@test.com"; - - mockMvc.perform(post("/usr/doctor-qna/write") - .contentType(MediaType.APPLICATION_JSON) - .param("questionTitle", dto.title()) - .param("content", dto.content()) - .principal(principal)) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("http://localhost/user/login")); - - verify(questionCommandService, times(1)).save(dto, principal); - } - -} diff --git a/src/test/java/com/codelion/animalcare/doctorqna/DoctorQnaTests.java b/src/test/java/com/codelion/animalcare/domain/doctorqna/DoctorQnaTests.java similarity index 99% rename from src/test/java/com/codelion/animalcare/doctorqna/DoctorQnaTests.java rename to src/test/java/com/codelion/animalcare/domain/doctorqna/DoctorQnaTests.java index 56d15841..c35608c6 100644 --- a/src/test/java/com/codelion/animalcare/doctorqna/DoctorQnaTests.java +++ b/src/test/java/com/codelion/animalcare/domain/doctorqna/DoctorQnaTests.java @@ -1,4 +1,4 @@ -package com.codelion.animalcare.doctorqna; +package com.codelion.animalcare.domain.doctorqna; /* import com.codelion.animalcare.domain.doctorqna.dto.request.AnswerSaveRequestDto; import com.codelion.animalcare.domain.doctorqna.dto.request.AnswerUpdateRequestDto; diff --git a/src/test/java/com/codelion/animalcare/domain/doctorqna/controller/QuestionControllerTest.java b/src/test/java/com/codelion/animalcare/domain/doctorqna/controller/QuestionControllerTest.java new file mode 100644 index 00000000..e5de7366 --- /dev/null +++ b/src/test/java/com/codelion/animalcare/domain/doctorqna/controller/QuestionControllerTest.java @@ -0,0 +1,128 @@ +package com.codelion.animalcare.domain.doctorqna.controller; + +import com.codelion.animalcare.domain.appointment.interceptor.HasAnimalsInterceptor; +import com.codelion.animalcare.domain.doctorqna.dto.request.QuestionSaveRequestDto; +import com.codelion.animalcare.domain.doctorqna.service.QuestionCommandService; +import com.codelion.animalcare.domain.doctorqna.service.QuestionQueryService; +import com.codelion.animalcare.domain.user.service.UserService; +import com.codelion.animalcare.global.config.MvcConfig; +import com.codelion.animalcare.webrtc.controller.WebrtcController; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.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.context.annotation.ComponentScan; +import org.springframework.context.annotation.FilterType; +import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.validation.BindingResult; + +import java.security.Principal; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +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.MockMvcResultMatchers.flash; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view; + +@MockBean(JpaMetamodelMappingContext.class) +@ExtendWith(MockitoExtension.class) +@WebMvcTest(controllers = QuestionController.class, + excludeFilters = { + @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = MvcConfig.class), + @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = HasAnimalsInterceptor.class), + @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = WebrtcController.class)}) +public class QuestionControllerTest { + + @MockBean + private QuestionCommandService questionCommandService; + + @MockBean + private QuestionQueryService questionQueryService; + + @MockBean + private UserService userService; + + @InjectMocks + private QuestionController questionController; + + @Mock + private BindingResult bindingResult; + + @Mock + private Principal principal; + + @Autowired + private MockMvc mockMvc; + + @DisplayName("권한이_없으면_Redirection_된다") + @Test + void saveForm_Fail() throws Exception { + + String viewName = questionController.saveForm(new QuestionSaveRequestDto("title", "content", null)); + + mockMvc.perform(get("/usr/doctor-qna/write")) + .andExpect(status().is3xxRedirection()); + + + } + + @DisplayName("QuestionController_질문작성폼_보여진다") + @WithMockUser(roles="MEMBER") + @Test + void saveForm_Success() throws Exception { + + String viewName = questionController.saveForm(new QuestionSaveRequestDto("title", "content", null)); + + mockMvc.perform(get("/usr/doctor-qna/write")) + .andExpect(status().isOk()) + .andExpect(view().name(viewName)); + + } + + @Test + @WithMockUser(roles="MEMBER") + void saveTest_withValidRequestDto() throws Exception { + // given + QuestionSaveRequestDto requestDto = new QuestionSaveRequestDto("title", "content", null); + + // when + mockMvc.perform(post("/usr/doctor-qna/write") + .param("title", requestDto.title()) + .param("content", requestDto.content())) + .andExpect(status().is3xxRedirection()) + .andExpect(view().name("redirect:/usr/doctor-qna")) + .andExpect(flash().attributeExists("message")); + + // then + verify(questionCommandService, times(1)).save(requestDto, any(Principal.class)); + } + + @Test + @WithMockUser(roles="MEMBER") + void saveTest_withInvalidRequestDto() throws Exception { + // given + QuestionSaveRequestDto requestDto = new QuestionSaveRequestDto("", "", null); + + // when + mockMvc.perform(post("/usr/doctor-qna/write") + .param("title", requestDto.title()) + .param("content", requestDto.content())) + .andExpect(status().isOk()) + .andExpect(view().name("doctorqna/doctorQnaQuestionForm")); + + // then + verifyNoInteractions(questionCommandService); + } +} + diff --git a/src/test/java/com/codelion/animalcare/doctorqna/service/AnswerCommandServiceTest.java b/src/test/java/com/codelion/animalcare/domain/doctorqna/service/AnswerCommandServiceTest.java similarity index 98% rename from src/test/java/com/codelion/animalcare/doctorqna/service/AnswerCommandServiceTest.java rename to src/test/java/com/codelion/animalcare/domain/doctorqna/service/AnswerCommandServiceTest.java index 41080619..dc7b6547 100644 --- a/src/test/java/com/codelion/animalcare/doctorqna/service/AnswerCommandServiceTest.java +++ b/src/test/java/com/codelion/animalcare/domain/doctorqna/service/AnswerCommandServiceTest.java @@ -1,4 +1,4 @@ -package com.codelion.animalcare.doctorqna.service; +package com.codelion.animalcare.domain.doctorqna.service; import com.codelion.animalcare.domain.doctorqna.dto.request.AnswerSaveRequestDto; import com.codelion.animalcare.domain.doctorqna.dto.request.AnswerUpdateRequestDto; diff --git a/src/test/java/com/codelion/animalcare/doctorqna/service/AnswerQueryServiceTest.java b/src/test/java/com/codelion/animalcare/domain/doctorqna/service/AnswerQueryServiceTest.java similarity index 97% rename from src/test/java/com/codelion/animalcare/doctorqna/service/AnswerQueryServiceTest.java rename to src/test/java/com/codelion/animalcare/domain/doctorqna/service/AnswerQueryServiceTest.java index 2e905d99..7ed51d46 100644 --- a/src/test/java/com/codelion/animalcare/doctorqna/service/AnswerQueryServiceTest.java +++ b/src/test/java/com/codelion/animalcare/domain/doctorqna/service/AnswerQueryServiceTest.java @@ -1,4 +1,4 @@ -package com.codelion.animalcare.doctorqna.service; +package com.codelion.animalcare.domain.doctorqna.service; import com.codelion.animalcare.domain.doctorqna.entity.Answer; import com.codelion.animalcare.domain.doctorqna.service.AnswerQueryService; diff --git a/src/test/java/com/codelion/animalcare/domain/doctorqna/service/HashtagServiceTest.java b/src/test/java/com/codelion/animalcare/domain/doctorqna/service/HashtagServiceTest.java new file mode 100644 index 00000000..3ffcdc8c --- /dev/null +++ b/src/test/java/com/codelion/animalcare/domain/doctorqna/service/HashtagServiceTest.java @@ -0,0 +1,43 @@ +package com.codelion.animalcare.domain.doctorqna.service; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.codelion.animalcare.domain.doctorqna.entity.Hashtag; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.transaction.annotation.Transactional; + +@ActiveProfiles("test") +@WebAppConfiguration +@Transactional +@SpringBootTest +class HashtagServiceTest { + + @Autowired + private QuestionQueryService questionQueryService; + @Autowired + private HashtagService hashtagService; + + @Test + @DisplayName("태그_저장된다 - 성공") + void save() { + + //given + String tagName = "강아지"; + hashtagService.save(tagName); + + //when + Hashtag hashtag = hashtagService.findByTagName(tagName) + .orElse(null); + + //then + assertNotNull(hashtag); + assertEquals(hashtag.getTagName(), "강아지"); + + } +} \ No newline at end of file diff --git a/src/test/java/com/codelion/animalcare/doctorqna/service/QuestionCommandServiceTest.java b/src/test/java/com/codelion/animalcare/domain/doctorqna/service/QuestionCommandServiceTest.java similarity index 73% rename from src/test/java/com/codelion/animalcare/doctorqna/service/QuestionCommandServiceTest.java rename to src/test/java/com/codelion/animalcare/domain/doctorqna/service/QuestionCommandServiceTest.java index 1d86d13d..fcc55a39 100644 --- a/src/test/java/com/codelion/animalcare/doctorqna/service/QuestionCommandServiceTest.java +++ b/src/test/java/com/codelion/animalcare/domain/doctorqna/service/QuestionCommandServiceTest.java @@ -1,11 +1,10 @@ -package com.codelion.animalcare.doctorqna.service; +package com.codelion.animalcare.domain.doctorqna.service; import com.codelion.animalcare.domain.doctorqna.dto.request.QuestionSaveRequestDto; import com.codelion.animalcare.domain.doctorqna.dto.request.QuestionUpdateRequestDto; import com.codelion.animalcare.domain.doctorqna.dto.response.QuestionResponseDto; import com.codelion.animalcare.domain.doctorqna.entity.Question; -import com.codelion.animalcare.domain.doctorqna.service.QuestionCommandService; -import com.codelion.animalcare.domain.doctorqna.service.QuestionQueryService; +import com.codelion.animalcare.domain.doctorqna.entity.QuestionHashtag; import com.codelion.animalcare.domain.user.service.MemberService; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -16,6 +15,8 @@ import org.springframework.transaction.annotation.Transactional; import java.security.Principal; +import java.util.Arrays; +import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -32,12 +33,14 @@ public class QuestionCommandServiceTest { private QuestionQueryService questionQueryService; @Autowired private MemberService memberService; + @Autowired + private QuestionHashtagService questionHashtagService; @DisplayName("질문_작성된다") @Test void t1() { //given - QuestionSaveRequestDto question = new QuestionSaveRequestDto("질문이 있습니다.", "테스트 코드는 어떻게 잘 짜나요?"); + QuestionSaveRequestDto question = new QuestionSaveRequestDto("질문이 있습니다.", "테스트 코드는 어떻게 잘 짜나요?", null); Principal principal = () -> "member1@test.com"; //when @@ -95,5 +98,31 @@ void t4() { //given assertThrows(IllegalArgumentException.class, () -> questionQueryService.findQuestionByQuestionId(3L)); } + + @DisplayName("해시태그_저장된다") + @Test + void t5() { + //given + + + List list = Arrays.asList("JAVA", "THYMELEAF"); + QuestionSaveRequestDto question = new QuestionSaveRequestDto("질문이 있습니다.", "테스트 코드는 어떻게 잘 짜나요?", list); + Principal principal = () -> "member1@test.com"; + + //when + questionCommandService.save(question, principal); + //Testinitdata -> 3 question + Question savedQuestion = questionQueryService.findQuestionByQuestionId(4L); + List hashtagList = questionHashtagService.findHashtagListByQuestion(savedQuestion); + + for(QuestionHashtag questionHashtag : hashtagList) { + System.out.println(questionHashtag.getHashtag().getTagName()); + } + + hashtagList.forEach(hashtag -> System.out.println(hashtag.getHashtag().getTagName())); + + assertEquals(hashtagList.size(), 2); + } + } diff --git a/src/test/java/com/codelion/animalcare/doctorqna/service/QuestionQueryServiceTest.java b/src/test/java/com/codelion/animalcare/domain/doctorqna/service/QuestionQueryServiceTest.java similarity index 94% rename from src/test/java/com/codelion/animalcare/doctorqna/service/QuestionQueryServiceTest.java rename to src/test/java/com/codelion/animalcare/domain/doctorqna/service/QuestionQueryServiceTest.java index cc52db45..b5b39abc 100644 --- a/src/test/java/com/codelion/animalcare/doctorqna/service/QuestionQueryServiceTest.java +++ b/src/test/java/com/codelion/animalcare/domain/doctorqna/service/QuestionQueryServiceTest.java @@ -1,9 +1,7 @@ -package com.codelion.animalcare.doctorqna.service; +package com.codelion.animalcare.domain.doctorqna.service; import com.codelion.animalcare.domain.doctorqna.dto.response.QuestionResponseDto; import com.codelion.animalcare.domain.doctorqna.entity.Question; -import com.codelion.animalcare.domain.doctorqna.service.QuestionCommandService; -import com.codelion.animalcare.domain.doctorqna.service.QuestionQueryService; import com.codelion.animalcare.domain.user.entity.Member; import com.codelion.animalcare.domain.user.service.MemberService; import org.junit.jupiter.api.DisplayName; @@ -122,4 +120,5 @@ void t6() { assertEquals("DESC", Objects.requireNonNull(questions.getSort().getOrderFor("createdAt")).getDirection().toString()); } + } \ No newline at end of file