diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 02bd405a..771ad5d7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -54,7 +54,7 @@ jobs: run: chmod +x ./gradlew - name: Build and Test with Gradle - run: ./gradlew bootJar -Pprofile=dev + run: ./gradlew bootJar -Pprofile=dev --info diff --git a/build.gradle b/build.gradle index 1ac7b2e5..a210ad53 100644 --- a/build.gradle +++ b/build.gradle @@ -89,6 +89,9 @@ dependencies { implementation 'org.opensearch.client:spring-data-opensearch:1.3.0' implementation 'org.opensearch.client:opensearch-java:2.9.1' + // mybatis + implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.4' + compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.h2database:h2' runtimeOnly 'org.mariadb.jdbc:mariadb-java-client' diff --git a/src/docs/asciidoc/api/mypage/comment-get.adoc b/src/docs/asciidoc/api/mypage/comment-get.adoc new file mode 100644 index 00000000..e8cf4d6e --- /dev/null +++ b/src/docs/asciidoc/api/mypage/comment-get.adoc @@ -0,0 +1,37 @@ +[[Comment-Get]] +== 내가 썼어요 댓글/답글 조회 API(GET: /devdevdev/api/v1/mypage/comments) + +* 회원이 작성한 댓글/답글을 조회한다. +* 최초 요청시 pickCommentId, techCommentId 는 가장 큰 숫자 값을 요청해야 합니다. + +=== 정상 요청/응답 + +==== HTTP Request + +include::{snippets}/mypage-comments/http-request.adoc[] + +==== HTTP Request Header Fields + +include::{snippets}/mypage-comments/request-headers.adoc[] + +==== HTTP Request Query Parameters Fields + +include::{snippets}/mypage-comments/query-parameters.adoc[] + +==== HTTP Response + +include::{snippets}/mypage-comments/http-response.adoc[] + +==== HTTP Response Fields + +include::{snippets}/mypage-comments/response-fields.adoc[] + +=== 예외 + +==== HTTP Response + +* `익명 회원은 사용할 수 없는 기능 입니다.`: 익명 회원인 경우 +* `회원을 찾을 수 없습니다.`: 회원이 존재하지 않는 경우 +* `유효하지 않은 회원 입니다.`: 회원이 유효하지 않은 경우 + +include::{snippets}/mypage-comments-member-exception/response-body.adoc[] \ No newline at end of file diff --git a/src/docs/asciidoc/api/mypage/mypage.adoc b/src/docs/asciidoc/api/mypage/mypage.adoc index 62cd90fb..d4b198dc 100644 --- a/src/docs/asciidoc/api/mypage/mypage.adoc +++ b/src/docs/asciidoc/api/mypage/mypage.adoc @@ -5,3 +5,4 @@ include::mypick-main.adoc[] include::exit-member.adoc[] include::exit-survey.adoc[] include::record-exit-survey.adoc[] +include::comment-get.adoc[] diff --git a/src/docs/asciidoc/api/tech-article/recommend.adoc b/src/docs/asciidoc/api/tech-article/recommend.adoc new file mode 100644 index 00000000..d206be14 --- /dev/null +++ b/src/docs/asciidoc/api/tech-article/recommend.adoc @@ -0,0 +1,22 @@ +[[TechArticleRecommend]] +== 기술블로그 추천 API(POST: /devdevdev/api/v1/articles/{techArticleId}/recommend) +* 회원과 익명회원은 기술블로그를 추천하거나 추천 취소할 수 있다. + +=== 정상 요청/응답 +==== HTTP Request +include::{snippets}/tech-article-recommend/http-request.adoc[] +==== HTTP Request Header Fields +include::{snippets}/tech-article-recommend/request-headers.adoc[] +==== HTTP Request Path Parameters Fields +include::{snippets}/tech-article-recommend/path-parameters.adoc[] + +==== HTTP Response +include::{snippets}/tech-article-recommend/http-response.adoc[] +==== HTTP Response Fields +include::{snippets}/tech-article-recommend/response-fields.adoc[] + + +=== 예외 +==== HTTP Response +include::{snippets}/not-found-tech-article-exception/response-body.adoc[] +include::{snippets}/not-found-member-exception/response-body.adoc[] \ No newline at end of file diff --git a/src/docs/asciidoc/api/tech-article/tech-article.adoc b/src/docs/asciidoc/api/tech-article/tech-article.adoc index d506f72d..3eddccf0 100644 --- a/src/docs/asciidoc/api/tech-article/tech-article.adoc +++ b/src/docs/asciidoc/api/tech-article/tech-article.adoc @@ -4,3 +4,4 @@ include::main.adoc[] include::detail.adoc[] include::bookmark.adoc[] include::keyword.adoc[] +include::recommend.adoc[] \ No newline at end of file diff --git a/src/main/java/com/dreamypatisiel/devdevdev/LocalInitData.java b/src/main/java/com/dreamypatisiel/devdevdev/LocalInitData.java index f3992592..8b42e96d 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/LocalInitData.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/LocalInitData.java @@ -23,7 +23,7 @@ import com.dreamypatisiel.devdevdev.domain.entity.enums.WordType; import com.dreamypatisiel.devdevdev.domain.policy.PickPopularScorePolicy; import com.dreamypatisiel.devdevdev.domain.repository.BlameTypeRepository; -import com.dreamypatisiel.devdevdev.domain.repository.BookmarkRepository; +import com.dreamypatisiel.devdevdev.domain.repository.techArticle.BookmarkRepository; import com.dreamypatisiel.devdevdev.domain.repository.CompanyRepository; import com.dreamypatisiel.devdevdev.domain.repository.member.MemberRepository; import com.dreamypatisiel.devdevdev.domain.repository.member.memberNicknameDictionary.MemberNicknameDictionaryRepository; diff --git a/src/main/java/com/dreamypatisiel/devdevdev/domain/entity/Pick.java b/src/main/java/com/dreamypatisiel/devdevdev/domain/entity/Pick.java index de705a94..85e69df7 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/domain/entity/Pick.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/domain/entity/Pick.java @@ -88,7 +88,7 @@ public class Pick extends BasicTime { private List embeddings; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "member_id") + @JoinColumn(name = "member_id", nullable = false) private Member member; @OneToMany(mappedBy = "pick") @@ -125,12 +125,13 @@ public static Pick create(Title title, String author, Member member) { pick.popularScore = Count.defaultCount(); pick.blameTotalCount = Count.defaultCount(); pick.author = author; - pick.contentStatus = getDefaultContentStatusByMemberRole(member); + pick.contentStatus = ContentStatus.APPROVAL; pick.member = member; return pick; } + @Deprecated // 신고 기능 추가로 인한 삭제 private static ContentStatus getDefaultContentStatusByMemberRole(Member member) { if (member.isAdmin()) { return ContentStatus.APPROVAL; diff --git a/src/main/java/com/dreamypatisiel/devdevdev/domain/entity/TechArticle.java b/src/main/java/com/dreamypatisiel/devdevdev/domain/entity/TechArticle.java index 06a65bf2..9b49e445 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/domain/entity/TechArticle.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/domain/entity/TechArticle.java @@ -1,6 +1,7 @@ package com.dreamypatisiel.devdevdev.domain.entity; import com.dreamypatisiel.devdevdev.domain.entity.embedded.Count; +import com.dreamypatisiel.devdevdev.domain.entity.embedded.Title; import com.dreamypatisiel.devdevdev.domain.entity.embedded.Url; import com.dreamypatisiel.devdevdev.domain.policy.TechArticlePopularScorePolicy; import com.dreamypatisiel.devdevdev.elastic.domain.document.ElasticTechArticle; @@ -35,6 +36,8 @@ public class TechArticle extends BasicTime { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + private Title title; + @Embedded @AttributeOverride(name = "count", column = @Column(name = "view_total_count") @@ -75,9 +78,14 @@ public class TechArticle extends BasicTime { @OneToMany(mappedBy = "techArticle") private List bookmarks = new ArrayList<>(); + @OneToMany(mappedBy = "techArticle") + private List recommends = new ArrayList<>(); + @Builder - private TechArticle(Count viewTotalCount, Count recommendTotalCount, Count commentTotalCount, Count popularScore, + private TechArticle(Title title, Count viewTotalCount, Count recommendTotalCount, Count commentTotalCount, + Count popularScore, Url techArticleUrl, Company company, String elasticId) { + this.title = title; this.techArticleUrl = techArticleUrl; this.viewTotalCount = viewTotalCount; this.recommendTotalCount = recommendTotalCount; @@ -89,6 +97,7 @@ private TechArticle(Count viewTotalCount, Count recommendTotalCount, Count comme public static TechArticle createTechArticle(ElasticTechArticle elasticTechArticle, Company company) { TechArticle techArticle = TechArticle.builder() + .title(new Title(elasticTechArticle.getTitle())) .techArticleUrl(new Url(elasticTechArticle.getTechArticleUrl())) .viewTotalCount(new Count(elasticTechArticle.getViewTotalCount())) .recommendTotalCount(new Count(elasticTechArticle.getRecommendTotalCount())) @@ -102,10 +111,11 @@ public static TechArticle createTechArticle(ElasticTechArticle elasticTechArticl return techArticle; } - public static TechArticle createTechArticle(Url techArticleUrl, Count viewTotalCount, Count recommendTotalCount, - Count commentTotalCount, - Count popularScore, String elasticId, Company company) { + public static TechArticle createTechArticle(Title title, Url techArticleUrl, Count viewTotalCount, + Count recommendTotalCount, Count commentTotalCount, Count popularScore, + String elasticId, Company company) { return TechArticle.builder() + .title(title) .techArticleUrl(techArticleUrl) .viewTotalCount(viewTotalCount) .recommendTotalCount(recommendTotalCount) @@ -127,6 +137,10 @@ public void changePopularScore(TechArticlePopularScorePolicy policy) { this.popularScore = this.calculatePopularScore(policy); } + private Count calculatePopularScore(TechArticlePopularScorePolicy policy) { + return policy.calculatePopularScore(this); + } + public void changeCompany(Company company) { company.getTechArticles().add(this); this.company = company; @@ -136,10 +150,6 @@ public void incrementViewTotalCount() { this.viewTotalCount = Count.plusOne(this.viewTotalCount); } - private Count calculatePopularScore(TechArticlePopularScorePolicy policy) { - return policy.calculatePopularScore(this); - } - public void incrementCommentCount() { this.commentTotalCount = Count.plusOne(this.commentTotalCount); } @@ -147,4 +157,13 @@ public void incrementCommentCount() { public void decrementCommentCount() { this.commentTotalCount = Count.minusOne(this.commentTotalCount); } + + + public void incrementRecommendTotalCount() { + this.recommendTotalCount = Count.plusOne(this.recommendTotalCount); + } + + public void decrementRecommendTotalCount() { + this.recommendTotalCount = Count.minusOne(this.recommendTotalCount); + } } diff --git a/src/main/java/com/dreamypatisiel/devdevdev/domain/entity/TechArticleRecommend.java b/src/main/java/com/dreamypatisiel/devdevdev/domain/entity/TechArticleRecommend.java index 69e3d2c6..d9302eb2 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/domain/entity/TechArticleRecommend.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/domain/entity/TechArticleRecommend.java @@ -1,15 +1,12 @@ package com.dreamypatisiel.devdevdev.domain.entity; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -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; +@Getter @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) public class TechArticleRecommend extends BasicTime { @@ -17,11 +14,68 @@ public class TechArticleRecommend extends BasicTime { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @Column(nullable = false) + private boolean status; + @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "member_id", nullable = false) + @JoinColumn(name = "member_id") private Member member; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "anonymous_member_id") + private AnonymousMember anonymousMember; + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "tech_article_id", nullable = false) private TechArticle techArticle; + + @Builder + private TechArticleRecommend(boolean status, Member member, AnonymousMember anonymousMember, TechArticle techArticle) { + this.status = status; + this.member = member; + this.anonymousMember = anonymousMember; + this.techArticle = techArticle; + } + + + public static TechArticleRecommend create(Member member, TechArticle techArticle) { + return TechArticleRecommend.builder() + .member(member) + .techArticle(techArticle) + .status(true) + .build(); + } + + public static TechArticleRecommend create(AnonymousMember anonymousMember, TechArticle techArticle) { + return TechArticleRecommend.builder() + .anonymousMember(anonymousMember) + .techArticle(techArticle) + .status(true) + .build(); + } + + public void changeTechArticle(TechArticle techArticle) { + this.techArticle = techArticle; + techArticle.getRecommends().add(this); + } + + public void cancelRecommend() { + this.status = false; + } + + public void registerRecommend() { + this.status = true; + } + + public boolean isRecommended() { + return this.status; + } + + public boolean isAnonymousMemberNotNull() { + return this.anonymousMember != null; + } + public boolean isMemberNotNull() { + return this.member != null; + } + } diff --git a/src/main/java/com/dreamypatisiel/devdevdev/domain/repository/comment/CommentRepository.java b/src/main/java/com/dreamypatisiel/devdevdev/domain/repository/comment/CommentRepository.java new file mode 100644 index 00000000..bf34f2b6 --- /dev/null +++ b/src/main/java/com/dreamypatisiel/devdevdev/domain/repository/comment/CommentRepository.java @@ -0,0 +1,61 @@ +package com.dreamypatisiel.devdevdev.domain.repository.comment; + +import com.dreamypatisiel.devdevdev.domain.repository.comment.mybatis.CommentMapper; +import com.dreamypatisiel.devdevdev.domain.repository.pick.PickCommentRepository; +import com.dreamypatisiel.devdevdev.domain.repository.techArticle.TechCommentRepository; +import com.dreamypatisiel.devdevdev.web.dto.SliceCustom; +import com.dreamypatisiel.devdevdev.web.dto.request.comment.MyWrittenCommentFilter; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class CommentRepository { + + private final CommentMapper commentMapper; + private final PickCommentRepository pickCommentRepository; + private final TechCommentRepository techCommentRepository; + + public SliceCustom findMyWrittenCommentsByCursor(Long memberId, + Long pickCommentId, + Long techCommentId, + MyWrittenCommentFilter myWrittenCommentSort, + Pageable pageable) { + + // 픽픽픽 + if (MyWrittenCommentFilter.PICK.equals(myWrittenCommentSort)) { + return pickCommentRepository.findMyWrittenPickCommentsByCursor(memberId, pickCommentId, pageable); + } + + // 기술블로그 + if (MyWrittenCommentFilter.TECH_ARTICLE.equals(myWrittenCommentSort)) { + return techCommentRepository.findMyWrittenTechCommentsByCursor(memberId, techCommentId, pageable); + } + + // 전체 + // 회원이 작성한 픽픽픽, 기술블로그 댓글 조회 + List findMyWrittenComments = commentMapper.findByMemberIdAndPickCommentIdAndTechCommentIdOrderByCommentCreatedAtDesc( + memberId, pickCommentId, techCommentId, pageable.getPageSize()); + + // 다음 페이지 존재 여부 + boolean hasNext = findMyWrittenComments.size() >= pageable.getPageSize(); + + // 회원이 작성한 댓글 총 갯수(삭제 미포함) + Long commentTotalCount = countByCreatedByIdAndDeletedAtIsNull(memberId); + + return new SliceCustom<>(findMyWrittenComments, pageable, hasNext, commentTotalCount); + } + + private Long countByCreatedByIdAndDeletedAtIsNull(Long createdById) { + + // 회원이 작성한 픽픽픽 댓글 갯수(삭제 미포함) + Long pickCommentTotal = pickCommentRepository.countByCreatedByIdAndDeletedAtIsNull(createdById); + + // 회원이 작성한 기술블로그 댓글 갯수(삭제 미포함) + Long techCommentTotal = techCommentRepository.countByCreatedByIdAndDeletedAtIsNull(createdById); + + return pickCommentTotal + techCommentTotal; + } +} diff --git a/src/main/java/com/dreamypatisiel/devdevdev/domain/repository/comment/MyWrittenCommentDto.java b/src/main/java/com/dreamypatisiel/devdevdev/domain/repository/comment/MyWrittenCommentDto.java new file mode 100644 index 00000000..e4d6df61 --- /dev/null +++ b/src/main/java/com/dreamypatisiel/devdevdev/domain/repository/comment/MyWrittenCommentDto.java @@ -0,0 +1,33 @@ +package com.dreamypatisiel.devdevdev.domain.repository.comment; + +import com.querydsl.core.annotations.QueryProjection; +import java.time.LocalDateTime; +import lombok.Data; + +@Data +public class MyWrittenCommentDto { + private Long postId; + private String postTitle; + private Long commentId; + private String commentType; + private String commentContents; + private Long commentRecommendTotalCount; + private LocalDateTime commentCreatedAt; + private String pickOptionTitle; + private String pickOptionType; + + @QueryProjection + public MyWrittenCommentDto(Long postId, String postTitle, Long commentId, String commentType, + String commentContents, Long commentRecommendTotalCount, LocalDateTime commentCreatedAt, + String pickOptionTitle, String pickOptionType) { + this.postId = postId; + this.postTitle = postTitle; + this.commentId = commentId; + this.commentType = commentType; + this.commentContents = commentContents; + this.commentRecommendTotalCount = commentRecommendTotalCount; + this.commentCreatedAt = commentCreatedAt; + this.pickOptionTitle = pickOptionTitle; + this.pickOptionType = pickOptionType; + } +} diff --git a/src/main/java/com/dreamypatisiel/devdevdev/domain/repository/comment/mybatis/CommentMapper.java b/src/main/java/com/dreamypatisiel/devdevdev/domain/repository/comment/mybatis/CommentMapper.java new file mode 100644 index 00000000..a8976693 --- /dev/null +++ b/src/main/java/com/dreamypatisiel/devdevdev/domain/repository/comment/mybatis/CommentMapper.java @@ -0,0 +1,15 @@ +package com.dreamypatisiel.devdevdev.domain.repository.comment.mybatis; + +import com.dreamypatisiel.devdevdev.domain.repository.comment.MyWrittenCommentDto; +import java.util.List; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +@Mapper +public interface CommentMapper { + List findByMemberIdAndPickCommentIdAndTechCommentIdOrderByCommentCreatedAtDesc( + @Param("memberId") Long memberId, + @Param("pickCommentId") Long pickCommentId, + @Param("techCommentId") Long techCommentId, + @Param("limit") int limit); +} diff --git a/src/main/java/com/dreamypatisiel/devdevdev/domain/repository/pick/PickCommentRepository.java b/src/main/java/com/dreamypatisiel/devdevdev/domain/repository/pick/PickCommentRepository.java index fb2f287f..85c9ebb9 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/domain/repository/pick/PickCommentRepository.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/domain/repository/pick/PickCommentRepository.java @@ -33,4 +33,6 @@ List findWithMemberWithPickWithPickVoteWithPickCommentRecommendsByO Long countByOriginParentIdIn(Set pickIds); Long countByPickIdAndOriginParentIsNullAndParentIsNullAndDeletedAtIsNull(Long pickId); + + Long countByCreatedByIdAndDeletedAtIsNull(Long createdById); } diff --git a/src/main/java/com/dreamypatisiel/devdevdev/domain/repository/pick/custom/PickCommentRepositoryCustom.java b/src/main/java/com/dreamypatisiel/devdevdev/domain/repository/pick/custom/PickCommentRepositoryCustom.java index 39b5b5ef..0aff36ae 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/domain/repository/pick/custom/PickCommentRepositoryCustom.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/domain/repository/pick/custom/PickCommentRepositoryCustom.java @@ -2,7 +2,9 @@ import com.dreamypatisiel.devdevdev.domain.entity.PickComment; import com.dreamypatisiel.devdevdev.domain.entity.enums.PickOptionType; +import com.dreamypatisiel.devdevdev.domain.repository.comment.MyWrittenCommentDto; import com.dreamypatisiel.devdevdev.domain.repository.pick.PickCommentSort; +import com.dreamypatisiel.devdevdev.web.dto.SliceCustom; import java.util.EnumSet; import java.util.List; import org.springframework.data.domain.Pageable; @@ -17,4 +19,7 @@ Slice findOriginParentPickCommentsByCursor(Pageable pageable, Long List findOriginParentPickCommentsByPickIdAndPickOptionTypeIn(Long pickId, EnumSet pickOptionTypes); + + SliceCustom findMyWrittenPickCommentsByCursor(Long memberId, Long pickCommentId, + Pageable pageable); } diff --git a/src/main/java/com/dreamypatisiel/devdevdev/domain/repository/pick/custom/PickCommentRepositoryImpl.java b/src/main/java/com/dreamypatisiel/devdevdev/domain/repository/pick/custom/PickCommentRepositoryImpl.java index e65a96c1..f67f74f5 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/domain/repository/pick/custom/PickCommentRepositoryImpl.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/domain/repository/pick/custom/PickCommentRepositoryImpl.java @@ -9,9 +9,14 @@ import com.dreamypatisiel.devdevdev.domain.entity.PickComment; import com.dreamypatisiel.devdevdev.domain.entity.enums.ContentStatus; import com.dreamypatisiel.devdevdev.domain.entity.enums.PickOptionType; +import com.dreamypatisiel.devdevdev.domain.repository.comment.MyWrittenCommentDto; +import com.dreamypatisiel.devdevdev.domain.repository.comment.QMyWrittenCommentDto; import com.dreamypatisiel.devdevdev.domain.repository.pick.PickCommentSort; +import com.dreamypatisiel.devdevdev.web.dto.SliceCustom; +import com.dreamypatisiel.devdevdev.web.dto.request.comment.MyWrittenCommentFilter; import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.core.types.dsl.Expressions; import com.querydsl.jpa.JPQLQueryFactory; import java.util.Arrays; import java.util.EnumSet; @@ -83,6 +88,41 @@ public List findOriginParentPickCommentsByPickIdAndPickOptionTypeIn .fetch(); } + @Override + public SliceCustom findMyWrittenPickCommentsByCursor(Long memberId, Long pickCommentId, + Pageable pageable) { + // 회원이 작성한 픽픽픽 댓글 조회 + List contents = query.select( + new QMyWrittenCommentDto(pick.id, + pick.title.title, + pickComment.id, + Expressions.constant(MyWrittenCommentFilter.PICK.name()), + pickComment.contents.commentContents, + pickComment.recommendTotalCount.count, + pickComment.createdAt, + pickOption.title.title, + pickOption.pickOptionType.stringValue())) + .from(pickComment) + .leftJoin(pickComment.pickVote, pickVote) + .leftJoin(pickVote.pickOption, pickOption) + .innerJoin(pick).on(pick.id.eq(pickComment.pick.id).and(pick.contentStatus.eq(ContentStatus.APPROVAL))) + .where(pickComment.createdBy.id.eq(memberId) + .and(pickComment.deletedAt.isNull()) + .and(pickComment.id.lt(pickCommentId))) + .orderBy(pickComment.createdAt.desc()) + .limit(pageable.getPageSize()) + .fetch(); + + // 회원이 작성한 픽픽픽 댓글 총 갯수(삭제 미포함) + long totalElements = query.select(pickComment.count()) + .from(pickComment) + .where(pickComment.createdBy.id.eq(memberId) + .and(pickComment.deletedAt.isNull())) + .fetchCount(); + + return new SliceCustom<>(contents, pageable, hasNextPage(contents, pageable.getPageSize()), totalElements); + } + private static BooleanExpression pickOptionTypeIn(EnumSet pickOptionTypes) { if (ObjectUtils.isEmpty(pickOptionTypes)) { return null; @@ -131,7 +171,7 @@ private OrderSpecifier pickCommentSort(PickCommentSort pickCommentSort) { .orElse(PickCommentSort.LATEST).getOrderSpecifierByPickCommentSort(); } - private boolean hasNextPage(List contents, int pageSize) { + private boolean hasNextPage(List contents, int pageSize) { return contents.size() >= pageSize; } } diff --git a/src/main/java/com/dreamypatisiel/devdevdev/domain/repository/BookmarkRepository.java b/src/main/java/com/dreamypatisiel/devdevdev/domain/repository/techArticle/BookmarkRepository.java similarity index 86% rename from src/main/java/com/dreamypatisiel/devdevdev/domain/repository/BookmarkRepository.java rename to src/main/java/com/dreamypatisiel/devdevdev/domain/repository/techArticle/BookmarkRepository.java index 7c622bee..74ed33c5 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/domain/repository/BookmarkRepository.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/domain/repository/techArticle/BookmarkRepository.java @@ -1,4 +1,4 @@ -package com.dreamypatisiel.devdevdev.domain.repository; +package com.dreamypatisiel.devdevdev.domain.repository.techArticle; import com.dreamypatisiel.devdevdev.domain.entity.Bookmark; import com.dreamypatisiel.devdevdev.domain.entity.Member; diff --git a/src/main/java/com/dreamypatisiel/devdevdev/domain/repository/techArticle/TechArticleRecommendRepository.java b/src/main/java/com/dreamypatisiel/devdevdev/domain/repository/techArticle/TechArticleRecommendRepository.java new file mode 100644 index 00000000..b111c7b3 --- /dev/null +++ b/src/main/java/com/dreamypatisiel/devdevdev/domain/repository/techArticle/TechArticleRecommendRepository.java @@ -0,0 +1,15 @@ +package com.dreamypatisiel.devdevdev.domain.repository.techArticle; + +import com.dreamypatisiel.devdevdev.domain.entity.AnonymousMember; +import com.dreamypatisiel.devdevdev.domain.entity.Member; +import com.dreamypatisiel.devdevdev.domain.entity.TechArticle; +import com.dreamypatisiel.devdevdev.domain.entity.TechArticleRecommend; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface TechArticleRecommendRepository extends JpaRepository { + Optional findByTechArticleAndMember(TechArticle techArticle, Member member); + + Optional findByTechArticleAndAnonymousMember(TechArticle techArticle, AnonymousMember anonymousMember); +} diff --git a/src/main/java/com/dreamypatisiel/devdevdev/domain/repository/techArticle/TechCommentRepository.java b/src/main/java/com/dreamypatisiel/devdevdev/domain/repository/techArticle/TechCommentRepository.java index 35e741b4..ef20d71c 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/domain/repository/techArticle/TechCommentRepository.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/domain/repository/techArticle/TechCommentRepository.java @@ -23,4 +23,6 @@ List findWithMemberWithTechArticleByOriginParentIdInAndParentIsNotN Set originParentIds); Long countByTechArticleIdAndOriginParentIsNullAndParentIsNullAndDeletedAtIsNull(Long techArticleId); + + Long countByCreatedByIdAndDeletedAtIsNull(Long createdById); } diff --git a/src/main/java/com/dreamypatisiel/devdevdev/domain/repository/techArticle/custom/TechCommentRepositoryCustom.java b/src/main/java/com/dreamypatisiel/devdevdev/domain/repository/techArticle/custom/TechCommentRepositoryCustom.java index f420e2f9..7ab763c0 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/domain/repository/techArticle/custom/TechCommentRepositoryCustom.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/domain/repository/techArticle/custom/TechCommentRepositoryCustom.java @@ -1,7 +1,9 @@ package com.dreamypatisiel.devdevdev.domain.repository.techArticle.custom; import com.dreamypatisiel.devdevdev.domain.entity.TechComment; +import com.dreamypatisiel.devdevdev.domain.repository.comment.MyWrittenCommentDto; import com.dreamypatisiel.devdevdev.domain.repository.techArticle.TechCommentSort; +import com.dreamypatisiel.devdevdev.web.dto.SliceCustom; import java.util.List; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; @@ -11,4 +13,7 @@ Slice findOriginParentTechCommentsByCursor(Long techArticleId, Long TechCommentSort techCommentSort, Pageable pageable); List findOriginParentTechBestCommentsByTechArticleIdAndOffset(Long techArticleId, int size); + + SliceCustom findMyWrittenTechCommentsByCursor(Long memberId, Long techCommentId, + Pageable pageable); } diff --git a/src/main/java/com/dreamypatisiel/devdevdev/domain/repository/techArticle/custom/TechCommentRepositoryImpl.java b/src/main/java/com/dreamypatisiel/devdevdev/domain/repository/techArticle/custom/TechCommentRepositoryImpl.java index 71f7a79f..581eed00 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/domain/repository/techArticle/custom/TechCommentRepositoryImpl.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/domain/repository/techArticle/custom/TechCommentRepositoryImpl.java @@ -1,24 +1,28 @@ package com.dreamypatisiel.devdevdev.domain.repository.techArticle.custom; +import static com.dreamypatisiel.devdevdev.domain.entity.QMember.member; +import static com.dreamypatisiel.devdevdev.domain.entity.QTechArticle.techArticle; +import static com.dreamypatisiel.devdevdev.domain.entity.QTechComment.techComment; + import com.dreamypatisiel.devdevdev.domain.entity.TechComment; +import com.dreamypatisiel.devdevdev.domain.repository.comment.MyWrittenCommentDto; +import com.dreamypatisiel.devdevdev.domain.repository.comment.QMyWrittenCommentDto; import com.dreamypatisiel.devdevdev.domain.repository.techArticle.TechCommentSort; +import com.dreamypatisiel.devdevdev.web.dto.SliceCustom; +import com.dreamypatisiel.devdevdev.web.dto.request.comment.MyWrittenCommentFilter; import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.core.types.dsl.Expressions; import com.querydsl.jpa.JPQLQueryFactory; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.domain.SliceImpl; import org.springframework.util.ObjectUtils; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; - -import static com.dreamypatisiel.devdevdev.domain.entity.QMember.member; -import static com.dreamypatisiel.devdevdev.domain.entity.QTechArticle.techArticle; -import static com.dreamypatisiel.devdevdev.domain.entity.QTechComment.techComment; - @RequiredArgsConstructor public class TechCommentRepositoryImpl implements TechCommentRepositoryCustom { @@ -59,6 +63,40 @@ public List findOriginParentTechBestCommentsByTechArticleIdAndOffse .fetch(); } + @Override + public SliceCustom findMyWrittenTechCommentsByCursor(Long memberId, Long techCommentId, + Pageable pageable) { + + // 회원이 작성한 기술블로그 댓글 조회 + List contents = query.select( + new QMyWrittenCommentDto(techArticle.id, + techArticle.title.title, + techComment.id, + Expressions.constant(MyWrittenCommentFilter.TECH_ARTICLE.name()), + techComment.contents.commentContents, + techComment.recommendTotalCount.count, + techComment.createdAt, + Expressions.nullExpression(), + Expressions.nullExpression())) + .from(techComment) + .innerJoin(techComment.techArticle, techArticle) + .where(techComment.createdBy.id.eq(memberId) + .and(techComment.deletedAt.isNull()) + .and(techComment.id.lt(techCommentId))) + .orderBy(techComment.createdAt.desc()) + .limit(pageable.getPageSize()) + .fetch(); + + // 회원이 작성한 기술 블로그 댓글 총 갯수(삭제 미포함) + long totalElements = query.select(techComment.count()) + .from(techComment) + .where(techComment.createdBy.id.eq(memberId) + .and(techComment.deletedAt.isNull())) + .fetchCount(); + + return new SliceCustom<>(contents, pageable, hasNextPage(contents, pageable.getPageSize()), totalElements); + } + private BooleanExpression getCursorCondition(TechCommentSort techCommentSort, Long techCommentId) { if (ObjectUtils.isEmpty(techCommentId)) { return null; @@ -87,7 +125,7 @@ private OrderSpecifier techCommentSort(TechCommentSort techCommentSort) { .orElse(TechCommentSort.LATEST).getOrderSpecifierByTechCommentSort(); } - private boolean hasNextPage(List contents, int pageSize) { + private boolean hasNextPage(List contents, int pageSize) { return contents.size() >= pageSize; } } diff --git a/src/main/java/com/dreamypatisiel/devdevdev/domain/service/member/AnonymousMemberService.java b/src/main/java/com/dreamypatisiel/devdevdev/domain/service/member/AnonymousMemberService.java new file mode 100644 index 00000000..0cd23599 --- /dev/null +++ b/src/main/java/com/dreamypatisiel/devdevdev/domain/service/member/AnonymousMemberService.java @@ -0,0 +1,34 @@ +package com.dreamypatisiel.devdevdev.domain.service.member; + +import com.dreamypatisiel.devdevdev.domain.entity.AnonymousMember; +import com.dreamypatisiel.devdevdev.domain.repository.member.AnonymousMemberRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; + +import static com.dreamypatisiel.devdevdev.domain.exception.PickExceptionMessage.INVALID_ANONYMOUS_MEMBER_ID_MESSAGE; + +@Transactional(readOnly = true) +@Service +@RequiredArgsConstructor +public class AnonymousMemberService { + + private final AnonymousMemberRepository anonymousMemberRepository; + + @Transactional + public AnonymousMember findOrCreateAnonymousMember(String anonymousMemberId) { + // 익명 사용자 검증 + validateAnonymousMemberId(anonymousMemberId); + + // 익명회원 조회 또는 생성 + return anonymousMemberRepository.findByAnonymousMemberId(anonymousMemberId) + .orElseGet(() -> anonymousMemberRepository.save(AnonymousMember.create(anonymousMemberId))); + } + + private void validateAnonymousMemberId(String anonymousMemberId) { + if (!StringUtils.hasText(anonymousMemberId)) { + throw new IllegalArgumentException(INVALID_ANONYMOUS_MEMBER_ID_MESSAGE); + } + } +} diff --git a/src/main/java/com/dreamypatisiel/devdevdev/domain/service/member/MemberService.java b/src/main/java/com/dreamypatisiel/devdevdev/domain/service/member/MemberService.java index ae37af75..ae0a1772 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/domain/service/member/MemberService.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/domain/service/member/MemberService.java @@ -10,6 +10,8 @@ import com.dreamypatisiel.devdevdev.domain.entity.SurveyVersionQuestionMapper; import com.dreamypatisiel.devdevdev.domain.entity.TechArticle; import com.dreamypatisiel.devdevdev.domain.entity.embedded.CustomSurveyAnswer; +import com.dreamypatisiel.devdevdev.domain.repository.comment.CommentRepository; +import com.dreamypatisiel.devdevdev.domain.repository.comment.MyWrittenCommentDto; import com.dreamypatisiel.devdevdev.domain.repository.pick.PickRepository; import com.dreamypatisiel.devdevdev.domain.repository.survey.SurveyAnswerRepository; import com.dreamypatisiel.devdevdev.domain.repository.survey.SurveyQuestionOptionRepository; @@ -17,18 +19,22 @@ import com.dreamypatisiel.devdevdev.domain.repository.survey.custom.SurveyAnswerJdbcTemplateRepository; import com.dreamypatisiel.devdevdev.domain.repository.techArticle.BookmarkSort; import com.dreamypatisiel.devdevdev.domain.repository.techArticle.TechArticleRepository; -import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.CompanyResponse; -import com.dreamypatisiel.devdevdev.web.dto.response.member.MemberExitSurveyQuestionResponse; -import com.dreamypatisiel.devdevdev.web.dto.response.member.MemberExitSurveyResponse; -import com.dreamypatisiel.devdevdev.web.dto.response.pick.MyPickMainResponse; -import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.TechArticleMainResponse; import com.dreamypatisiel.devdevdev.domain.service.techArticle.techArticle.TechArticleCommonService; import com.dreamypatisiel.devdevdev.elastic.domain.document.ElasticTechArticle; import com.dreamypatisiel.devdevdev.exception.SurveyException; import com.dreamypatisiel.devdevdev.global.common.MemberProvider; import com.dreamypatisiel.devdevdev.global.common.TimeProvider; +import com.dreamypatisiel.devdevdev.web.dto.SliceCustom; +import com.dreamypatisiel.devdevdev.web.dto.request.comment.MyWrittenCommentFilter; +import com.dreamypatisiel.devdevdev.web.dto.request.comment.MyWrittenCommentRequest; import com.dreamypatisiel.devdevdev.web.dto.request.member.RecordMemberExitSurveyAnswerRequest; import com.dreamypatisiel.devdevdev.web.dto.request.member.RecordMemberExitSurveyQuestionOptionsRequest; +import com.dreamypatisiel.devdevdev.web.dto.response.comment.MyWrittenCommentResponse; +import com.dreamypatisiel.devdevdev.web.dto.response.member.MemberExitSurveyQuestionResponse; +import com.dreamypatisiel.devdevdev.web.dto.response.member.MemberExitSurveyResponse; +import com.dreamypatisiel.devdevdev.web.dto.response.pick.MyPickMainResponse; +import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.CompanyResponse; +import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.TechArticleMainResponse; import java.util.List; import java.util.Map; import java.util.Objects; @@ -55,6 +61,7 @@ public class MemberService { private final TimeProvider timeProvider; private final SurveyQuestionOptionRepository surveyQuestionOptionRepository; private final SurveyAnswerJdbcTemplateRepository surveyAnswerJdbcTemplateRepository; + private final CommentRepository commentRepository; /** * 회원 탈퇴 회원의 북마크와 회원 정보를 삭제합니다. @@ -169,11 +176,12 @@ public void recordMemberExitSurveyAnswer(RecordMemberExitSurveyAnswerRequest rec private SurveyAnswer createSurveyAnswerBy(SurveyQuestionOption option, Map surveyQuestionOptions, Member findMember) { + if (!surveyQuestionOptions.containsKey(option.getId())) { return null; } - // surveyQuestionOptions 에 알맞은 키의 값이 존재하면 + // surveyQuestionOptions 에 알맞은 키의 값이 존재하면 String message = surveyQuestionOptions.get(option.getId()).getMessage(); SurveyQuestion surveyQuestion = option.getSurveyQuestion(); @@ -188,7 +196,6 @@ private SurveyAnswer createSurveyAnswerBy(SurveyQuestionOption option, surveyQuestion, option); } - /** * 회원 자신이 북마크한 기술블로그를 조회합니다. */ @@ -226,4 +233,34 @@ private Stream mapToTechArticlesResponse(TechArticle te .map(elasticTechArticle -> TechArticleMainResponse.of(techArticle, elasticTechArticle, CompanyResponse.from(techArticle.getCompany()), member)); } + + /** + * @Note: 회원이 작성한 댓글을 조회합니다.(삭제된 댓글 미포함) + * @Author: 장세웅 + * @Since: 2024.12.31 + */ + public SliceCustom findMyWrittenComments(Pageable pageable, + MyWrittenCommentRequest myWrittenCommentRequest, + Authentication authentication) { + + Long pickCommentId = myWrittenCommentRequest.getPickCommentId(); + Long techCommentId = myWrittenCommentRequest.getTechCommentId(); + MyWrittenCommentFilter myWrittenCommentSort = myWrittenCommentRequest.getCommentFilter(); + + // 회원 조회 + Member findMember = memberProvider.getMemberByAuthentication(authentication); + + // 회원이 작성한 댓글 조회 + SliceCustom findMyWrittenCommentsDto = commentRepository.findMyWrittenCommentsByCursor( + findMember.getId(), pickCommentId, techCommentId, myWrittenCommentSort, pageable); + + // 데이터 가공 + List myWrittenCommentResponses = MyWrittenCommentResponse.from( + findMyWrittenCommentsDto.getContent()); + + boolean hasNext = findMyWrittenCommentsDto.hasNext(); + long totalElements = findMyWrittenCommentsDto.getTotalElements(); + + return new SliceCustom<>(myWrittenCommentResponses, pageable, hasNext, totalElements); + } } diff --git a/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/GuestPickService.java b/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/GuestPickService.java index 153b2da6..45e2ccd1 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/GuestPickService.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/GuestPickService.java @@ -1,10 +1,5 @@ package com.dreamypatisiel.devdevdev.domain.service.pick; -import static com.dreamypatisiel.devdevdev.domain.exception.PickExceptionMessage.INVALID_ANONYMOUS_MEMBER_ID_MESSAGE; -import static com.dreamypatisiel.devdevdev.domain.exception.PickExceptionMessage.INVALID_CAN_NOT_VOTE_SAME_PICK_OPTION_MESSAGE; -import static com.dreamypatisiel.devdevdev.domain.exception.PickExceptionMessage.INVALID_NOT_APPROVAL_STATUS_PICK_MESSAGE; -import static com.dreamypatisiel.devdevdev.domain.exception.PickExceptionMessage.INVALID_NOT_FOUND_PICK_MESSAGE; - import com.dreamypatisiel.devdevdev.domain.entity.AnonymousMember; import com.dreamypatisiel.devdevdev.domain.entity.Pick; import com.dreamypatisiel.devdevdev.domain.entity.PickOption; @@ -13,12 +8,8 @@ import com.dreamypatisiel.devdevdev.domain.entity.enums.PickOptionType; import com.dreamypatisiel.devdevdev.domain.policy.PickBestCommentsPolicy; import com.dreamypatisiel.devdevdev.domain.policy.PickPopularScorePolicy; -import com.dreamypatisiel.devdevdev.domain.repository.member.AnonymousMemberRepository; -import com.dreamypatisiel.devdevdev.domain.repository.pick.PickCommentRecommendRepository; -import com.dreamypatisiel.devdevdev.domain.repository.pick.PickCommentRepository; -import com.dreamypatisiel.devdevdev.domain.repository.pick.PickRepository; -import com.dreamypatisiel.devdevdev.domain.repository.pick.PickSort; -import com.dreamypatisiel.devdevdev.domain.repository.pick.PickVoteRepository; +import com.dreamypatisiel.devdevdev.domain.repository.pick.*; +import com.dreamypatisiel.devdevdev.domain.service.member.AnonymousMemberService; import com.dreamypatisiel.devdevdev.domain.service.pick.dto.VotePickOptionDto; import com.dreamypatisiel.devdevdev.exception.NotFoundException; import com.dreamypatisiel.devdevdev.exception.VotePickOptionException; @@ -27,20 +18,7 @@ import com.dreamypatisiel.devdevdev.openai.embeddings.EmbeddingsService; import com.dreamypatisiel.devdevdev.web.dto.request.pick.ModifyPickRequest; import com.dreamypatisiel.devdevdev.web.dto.request.pick.RegisterPickRequest; -import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickDetailOptionResponse; -import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickDetailResponse; -import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickMainResponse; -import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickModifyResponse; -import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickRegisterResponse; -import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickUploadImageResponse; -import com.dreamypatisiel.devdevdev.web.dto.response.pick.SimilarPickResponse; -import com.dreamypatisiel.devdevdev.web.dto.response.pick.VotePickOptionResponse; -import com.dreamypatisiel.devdevdev.web.dto.response.pick.VotePickResponse; -import java.math.BigDecimal; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.stream.Collectors; +import com.dreamypatisiel.devdevdev.web.dto.response.pick.*; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.domain.SliceImpl; @@ -48,9 +26,16 @@ import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.StringUtils; import org.springframework.web.multipart.MultipartFile; +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import static com.dreamypatisiel.devdevdev.domain.exception.PickExceptionMessage.*; + @Service @Transactional(readOnly = true) public class GuestPickService extends PickCommonService implements PickService { @@ -60,8 +45,8 @@ public class GuestPickService extends PickCommonService implements PickService { private final PickPopularScorePolicy pickPopularScorePolicy; private final PickVoteRepository pickVoteRepository; - private final AnonymousMemberRepository anonymousMemberRepository; private final TimeProvider timeProvider; + private final AnonymousMemberService anonymousMemberService; public GuestPickService(PickRepository pickRepository, EmbeddingsService embeddingsService, PickBestCommentsPolicy pickBestCommentsPolicy, @@ -69,12 +54,12 @@ public GuestPickService(PickRepository pickRepository, EmbeddingsService embeddi PickCommentRecommendRepository pickCommentRecommendRepository, PickPopularScorePolicy pickPopularScorePolicy, PickVoteRepository pickVoteRepository, - AnonymousMemberRepository anonymousMemberRepository, TimeProvider timeProvider) { + TimeProvider timeProvider, AnonymousMemberService anonymousMemberService) { super(embeddingsService, pickBestCommentsPolicy, pickRepository, pickCommentRepository, pickCommentRecommendRepository); this.pickPopularScorePolicy = pickPopularScorePolicy; this.pickVoteRepository = pickVoteRepository; - this.anonymousMemberRepository = anonymousMemberRepository; + this.anonymousMemberService = anonymousMemberService; this.timeProvider = timeProvider; } @@ -86,7 +71,7 @@ public Slice findPicksMain(Pageable pageable, Long pickId, Pic AuthenticationMemberUtils.validateAnonymousMethodCall(authentication); // anonymousMemberId 검증 - AnonymousMember anonymousMember = findOrCreateAnonymousMember(anonymousMemberId); + AnonymousMember anonymousMember = anonymousMemberService.findOrCreateAnonymousMember(anonymousMemberId); // 픽픽픽 조회 Slice picks = pickRepository.findPicksByCursor(pageable, pickId, pickSort); @@ -123,7 +108,7 @@ public PickDetailResponse findPickDetail(Long pickId, String anonymousMemberId, AuthenticationMemberUtils.validateAnonymousMethodCall(authentication); // 익명 회원 조회 또는 생성 - AnonymousMember anonymousMember = findOrCreateAnonymousMember(anonymousMemberId); + AnonymousMember anonymousMember = anonymousMemberService.findOrCreateAnonymousMember(anonymousMemberId); // 픽픽픽 상세 조회(pickOption 페치조인) Pick findPick = pickRepository.findPickWithPickOptionByPickId(pickId) @@ -146,15 +131,6 @@ public PickDetailResponse findPickDetail(Long pickId, String anonymousMemberId, return PickDetailResponse.of(findPick, findPick.getMember(), anonymousMember, pickDetailOptions); } - private AnonymousMember findOrCreateAnonymousMember(String anonymousMemberId) { - // 익명 사용자 검증 - validateAnonymousMemberId(anonymousMemberId); - - // 익명회원 조회 또는 생성 - return anonymousMemberRepository.findByAnonymousMemberId(anonymousMemberId) - .orElseGet(() -> anonymousMemberRepository.save(AnonymousMember.create(anonymousMemberId))); - } - /** * 익명 회원이 픽픽픽을 투표한다. */ @@ -171,7 +147,7 @@ public VotePickResponse votePickOption(VotePickOptionDto votePickOptionDto, String anonymousMemberId = votePickOptionDto.getAnonymousMemberId(); // 익명 회원을 조회하거나 생성 - AnonymousMember anonymousMember = findOrCreateAnonymousMember(anonymousMemberId); + AnonymousMember anonymousMember = anonymousMemberService.findOrCreateAnonymousMember(anonymousMemberId); Optional pickVoteOptional = pickVoteRepository.findWithPickAndPickOptionByPickIdAndAnonymousMemberAndDeletedAtIsNull( pickId, anonymousMember); @@ -300,12 +276,6 @@ public List findTop3SimilarPicks(Long pickId) { return super.findTop3SimilarPicks(pickId); } - private void validateAnonymousMemberId(String anonymousMemberId) { - if (!StringUtils.hasText(anonymousMemberId)) { - throw new IllegalArgumentException(INVALID_ANONYMOUS_MEMBER_ID_MESSAGE); - } - } - @Override public void deleteImage(Long pickOptionImageId) { throw new AccessDeniedException(INVALID_ANONYMOUS_CAN_NOT_USE_THIS_FUNCTION_MESSAGE); diff --git a/src/main/java/com/dreamypatisiel/devdevdev/domain/service/techArticle/techArticle/GuestTechArticleService.java b/src/main/java/com/dreamypatisiel/devdevdev/domain/service/techArticle/techArticle/GuestTechArticleService.java index 89e0fd8a..b593c468 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/domain/service/techArticle/techArticle/GuestTechArticleService.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/domain/service/techArticle/techArticle/GuestTechArticleService.java @@ -1,24 +1,20 @@ package com.dreamypatisiel.devdevdev.domain.service.techArticle.techArticle; -import static com.dreamypatisiel.devdevdev.web.dto.util.TechArticleResponseUtils.hasNextPage; - +import com.dreamypatisiel.devdevdev.domain.entity.AnonymousMember; import com.dreamypatisiel.devdevdev.domain.entity.TechArticle; +import com.dreamypatisiel.devdevdev.domain.entity.TechArticleRecommend; import com.dreamypatisiel.devdevdev.domain.policy.TechArticlePopularScorePolicy; +import com.dreamypatisiel.devdevdev.domain.repository.techArticle.TechArticleRecommendRepository; import com.dreamypatisiel.devdevdev.domain.repository.techArticle.TechArticleRepository; import com.dreamypatisiel.devdevdev.domain.repository.techArticle.TechArticleSort; +import com.dreamypatisiel.devdevdev.domain.service.member.AnonymousMemberService; import com.dreamypatisiel.devdevdev.elastic.data.response.ElasticResponse; import com.dreamypatisiel.devdevdev.elastic.domain.document.ElasticTechArticle; import com.dreamypatisiel.devdevdev.elastic.domain.repository.ElasticTechArticleRepository; import com.dreamypatisiel.devdevdev.elastic.domain.service.ElasticTechArticleService; import com.dreamypatisiel.devdevdev.global.utils.AuthenticationMemberUtils; import com.dreamypatisiel.devdevdev.web.dto.SliceCustom; -import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.BookmarkResponse; -import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.CompanyResponse; -import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.TechArticleDetailResponse; -import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.TechArticleMainResponse; -import java.util.Collections; -import java.util.List; -import java.util.stream.Stream; +import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.*; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; @@ -27,23 +23,39 @@ import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.ObjectUtils; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +import static com.dreamypatisiel.devdevdev.web.dto.util.TechArticleResponseUtils.hasNextPage; @Slf4j @Service +@Transactional(readOnly = true) public class GuestTechArticleService extends TechArticleCommonService implements TechArticleService { public static final String INVALID_ANONYMOUS_CAN_NOT_USE_THIS_FUNCTION_MESSAGE = "비회원은 현재 해당 기능을 이용할 수 없습니다."; private final ElasticTechArticleService elasticTechArticleService; private final TechArticlePopularScorePolicy techArticlePopularScorePolicy; + private final AnonymousMemberService anonymousMemberService; + private final TechArticleRecommendRepository techArticleRecommendRepository; public GuestTechArticleService(TechArticleRepository techArticleRepository, ElasticTechArticleRepository elasticTechArticleRepository, ElasticTechArticleService elasticTechArticleService, - TechArticlePopularScorePolicy techArticlePopularScorePolicy) { + TechArticlePopularScorePolicy techArticlePopularScorePolicy, + AnonymousMemberService anonymousMemberService, + TechArticleRecommendRepository techArticleRecommendRepository + ) { super(techArticleRepository, elasticTechArticleRepository); this.elasticTechArticleService = elasticTechArticleService; this.techArticlePopularScorePolicy = techArticlePopularScorePolicy; + this.anonymousMemberService = anonymousMemberService; + this.techArticleRecommendRepository = techArticleRecommendRepository; } @Override @@ -68,10 +80,12 @@ public Slice getTechArticles(Pageable pageable, String @Override @Transactional - public TechArticleDetailResponse getTechArticle(Long techArticleId, Authentication authentication) { + public TechArticleDetailResponse getTechArticle(Long techArticleId, String anonymousMemberId, Authentication authentication) { // 익명 사용자 호출인지 확인 AuthenticationMemberUtils.validateAnonymousMethodCall(authentication); + // 익명 회원을 조회하거나 생성 + AnonymousMember anonymousMember = getAnonymousMemberOrNull(anonymousMemberId); // 기술블로그 조회 TechArticle techArticle = findTechArticle(techArticleId); ElasticTechArticle elasticTechArticle = findElasticTechArticle(techArticle); @@ -82,7 +96,7 @@ public TechArticleDetailResponse getTechArticle(Long techArticleId, Authenticati techArticle.changePopularScore(techArticlePopularScorePolicy); // 데이터 가공 - return TechArticleDetailResponse.of(techArticle, elasticTechArticle, companyResponse); + return TechArticleDetailResponse.of(techArticle, elasticTechArticle, companyResponse, anonymousMember); } @Override @@ -90,6 +104,55 @@ public BookmarkResponse updateBookmark(Long techArticleId, Authentication authen throw new AccessDeniedException(INVALID_ANONYMOUS_CAN_NOT_USE_THIS_FUNCTION_MESSAGE); } + @Override + @Transactional + public TechArticleRecommendResponse updateRecommend(Long techArticleId, String anonymousMemberId, Authentication authentication) { + // 익명 사용자 호출인지 확인 + AuthenticationMemberUtils.validateAnonymousMethodCall(authentication); + + // 익명 회원을 조회하거나 생성 + AnonymousMember anonymousMember = anonymousMemberService.findOrCreateAnonymousMember(anonymousMemberId); + + // 익명회원의 해당 기술블로그 아티클 추천 조회 + TechArticle techArticle = findTechArticle(techArticleId); + Optional optionalTechArticleRecommend = techArticleRecommendRepository.findByTechArticleAndAnonymousMember(techArticle, anonymousMember); + + // 추천이 존재하면 toggle + if (optionalTechArticleRecommend.isPresent()) { + TechArticleRecommend techArticleRecommend = optionalTechArticleRecommend.get(); + + // 추천 상태라면 추천 취소 + if (techArticleRecommend.isRecommended()) { + techArticleRecommend.cancelRecommend(); + + // 기술블로그 추천 수 감소 및 점수 변경 + techArticle.decrementRecommendTotalCount(); + techArticle.changePopularScore(techArticlePopularScorePolicy); + + return new TechArticleRecommendResponse(techArticle.getId(), techArticleRecommend.isRecommended(), techArticle.getRecommendTotalCount().getCount()); + } + + // 추천 상태가 아니라면 추천 + techArticleRecommend.registerRecommend(); + + // 기술블로그 추천 수 증가 및 점수 변경 + techArticle.incrementRecommendTotalCount(); + techArticle.changePopularScore(techArticlePopularScorePolicy); + + return new TechArticleRecommendResponse(techArticle.getId(), techArticleRecommend.isRecommended(), techArticle.getRecommendTotalCount().getCount()); + } + + // 추천 생성 + TechArticleRecommend techArticleRecommend = TechArticleRecommend.create(anonymousMember, techArticle); + techArticleRecommendRepository.save(techArticleRecommend); + + // 기술블로그 추천 수 증가 및 점수 변경 + techArticle.incrementRecommendTotalCount(); + techArticle.changePopularScore(techArticlePopularScorePolicy); + + return new TechArticleRecommendResponse(techArticle.getId(), techArticleRecommend.isRecommended(), techArticle.getRecommendTotalCount().getCount()); + } + /** * 엘라스틱서치 검색 결과로 기술블로그 목록 응답을 생성합니다. */ @@ -117,4 +180,14 @@ private Stream mapToTechArticlesResponse(TechArticle te .map(elasticTechArticle -> TechArticleMainResponse.of(techArticle, elasticTechArticle.content(), CompanyResponse.from(techArticle.getCompany()), elasticTechArticle.score())); } + + /** + * anonymousMemberId가 있으면 익명 회원을 조회 또는 생성하고, 없으면 null 반환 + */ + public AnonymousMember getAnonymousMemberOrNull(String anonymousMemberId) { + if(!ObjectUtils.isEmpty(anonymousMemberId)) { + return anonymousMemberService.findOrCreateAnonymousMember(anonymousMemberId); + } + return null; + } } diff --git a/src/main/java/com/dreamypatisiel/devdevdev/domain/service/techArticle/techArticle/MemberTechArticleService.java b/src/main/java/com/dreamypatisiel/devdevdev/domain/service/techArticle/techArticle/MemberTechArticleService.java index 49762dbc..34ec9e46 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/domain/service/techArticle/techArticle/MemberTechArticleService.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/domain/service/techArticle/techArticle/MemberTechArticleService.java @@ -5,8 +5,10 @@ import com.dreamypatisiel.devdevdev.domain.entity.Bookmark; import com.dreamypatisiel.devdevdev.domain.entity.Member; import com.dreamypatisiel.devdevdev.domain.entity.TechArticle; +import com.dreamypatisiel.devdevdev.domain.entity.TechArticleRecommend; import com.dreamypatisiel.devdevdev.domain.policy.TechArticlePopularScorePolicy; -import com.dreamypatisiel.devdevdev.domain.repository.BookmarkRepository; +import com.dreamypatisiel.devdevdev.domain.repository.techArticle.BookmarkRepository; +import com.dreamypatisiel.devdevdev.domain.repository.techArticle.TechArticleRecommendRepository; import com.dreamypatisiel.devdevdev.domain.repository.techArticle.TechArticleRepository; import com.dreamypatisiel.devdevdev.domain.repository.techArticle.TechArticleSort; import com.dreamypatisiel.devdevdev.elastic.data.response.ElasticResponse; @@ -15,10 +17,8 @@ import com.dreamypatisiel.devdevdev.elastic.domain.service.ElasticTechArticleService; import com.dreamypatisiel.devdevdev.global.common.MemberProvider; import com.dreamypatisiel.devdevdev.web.dto.SliceCustom; -import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.BookmarkResponse; -import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.CompanyResponse; -import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.TechArticleDetailResponse; -import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.TechArticleMainResponse; +import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.*; + import java.util.Collections; import java.util.List; import java.util.Optional; @@ -39,18 +39,20 @@ public class MemberTechArticleService extends TechArticleCommonService implement private final ElasticTechArticleService elasticTechArticleService; private final TechArticlePopularScorePolicy techArticlePopularScorePolicy; private final BookmarkRepository bookmarkRepository; + private final TechArticleRecommendRepository techArticleRecommendRepository; private final MemberProvider memberProvider; public MemberTechArticleService(TechArticleRepository techArticleRepository, ElasticTechArticleRepository elasticTechArticleRepository, ElasticTechArticleService elasticTechArticleService, TechArticlePopularScorePolicy techArticlePopularScorePolicy, - BookmarkRepository bookmarkRepository, + BookmarkRepository bookmarkRepository, TechArticleRecommendRepository techArticleRecommendRepository, MemberProvider memberProvider) { super(techArticleRepository, elasticTechArticleRepository); this.elasticTechArticleService = elasticTechArticleService; this.techArticlePopularScorePolicy = techArticlePopularScorePolicy; this.bookmarkRepository = bookmarkRepository; + this.techArticleRecommendRepository = techArticleRecommendRepository; this.memberProvider = memberProvider; } @@ -76,7 +78,7 @@ public Slice getTechArticles(Pageable pageable, String @Override @Transactional - public TechArticleDetailResponse getTechArticle(Long techArticleId, Authentication authentication) { + public TechArticleDetailResponse getTechArticle(Long techArticleId, String anonymousMemberId, Authentication authentication) { // 회원 조회 Member member = memberProvider.getMemberByAuthentication(authentication); @@ -125,6 +127,54 @@ public BookmarkResponse updateBookmark(Long techArticleId, Authentication authen return new BookmarkResponse(techArticle.getId(), bookmark.isBookmarked()); } + @Override + @Transactional + public TechArticleRecommendResponse updateRecommend(Long techArticleId, String anonymousMemberId, Authentication authentication) { + // 회원 조회 + Member member = memberProvider.getMemberByAuthentication(authentication); + + // 회원의 해당 기술블로그 아티클 추천 조회 + TechArticle techArticle = findTechArticle(techArticleId); + + Optional optionalTechArticleRecommend = techArticleRecommendRepository.findByTechArticleAndMember(techArticle, member); + + // 추천이 존재하면 toggle + if (optionalTechArticleRecommend.isPresent()) { + TechArticleRecommend techArticleRecommend = optionalTechArticleRecommend.get(); + + // 추천 상태라면 추천 취소 + if (techArticleRecommend.isRecommended()) { + techArticleRecommend.cancelRecommend(); + + // 기술블로그 추천 수 감소 및 점수 변경 + techArticle.decrementRecommendTotalCount(); + techArticle.changePopularScore(techArticlePopularScorePolicy); + + return new TechArticleRecommendResponse(techArticle.getId(), techArticleRecommend.isRecommended(), techArticle.getRecommendTotalCount().getCount()); + } + + // 추천 상태가 아니라면 추천 + techArticleRecommend.registerRecommend(); + + // 기술블로그 추천 수 증가 및 점수 변경 + techArticle.incrementRecommendTotalCount(); + techArticle.changePopularScore(techArticlePopularScorePolicy); + + return new TechArticleRecommendResponse(techArticle.getId(), techArticleRecommend.isRecommended(), techArticle.getRecommendTotalCount().getCount()); + } + + // 추천 생성 + TechArticleRecommend techArticleRecommend = TechArticleRecommend.create(member, techArticle); + + // 기술블로그 추천 수 증가 및 점수 변경 + techArticle.incrementRecommendTotalCount(); + techArticle.changePopularScore(techArticlePopularScorePolicy); + + techArticleRecommendRepository.save(techArticleRecommend); + + return new TechArticleRecommendResponse(techArticle.getId(), techArticleRecommend.isRecommended(), techArticle.getRecommendTotalCount().getCount()); + } + /** * 엘라스틱서치 검색 결과로 기술블로그 목록 응답을 생성합니다. */ diff --git a/src/main/java/com/dreamypatisiel/devdevdev/domain/service/techArticle/techArticle/TechArticleService.java b/src/main/java/com/dreamypatisiel/devdevdev/domain/service/techArticle/techArticle/TechArticleService.java index dad05826..48a9f41d 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/domain/service/techArticle/techArticle/TechArticleService.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/domain/service/techArticle/techArticle/TechArticleService.java @@ -4,17 +4,19 @@ import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.BookmarkResponse; import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.TechArticleDetailResponse; import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.TechArticleMainResponse; +import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.TechArticleRecommendResponse; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.security.core.Authentication; -import org.springframework.stereotype.Service; public interface TechArticleService { Slice getTechArticles(Pageable pageable, String elasticId, TechArticleSort techArticleSort, String keyword, Long companyId, Float score, Authentication authentication); - TechArticleDetailResponse getTechArticle(Long techArticleId, Authentication authentication); + TechArticleDetailResponse getTechArticle(Long techArticleId, String anonymousMemberId, Authentication authentication); BookmarkResponse updateBookmark(Long techArticleId, Authentication authentication); + + TechArticleRecommendResponse updateRecommend(Long techArticleId, String anonymousMemberId, Authentication authentication); } \ No newline at end of file diff --git a/src/main/java/com/dreamypatisiel/devdevdev/global/security/jwt/model/JwtCookieConstant.java b/src/main/java/com/dreamypatisiel/devdevdev/global/security/jwt/model/JwtCookieConstant.java index 22b7f4e8..1c14122f 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/global/security/jwt/model/JwtCookieConstant.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/global/security/jwt/model/JwtCookieConstant.java @@ -6,4 +6,5 @@ public class JwtCookieConstant { public static final String DEVDEVDEV_LOGIN_STATUS = "DEVDEVDEV_LOGIN_STATUS"; public static final String DEVDEVDEV_MEMBER_NICKNAME = "DEVDEVDEV_MEMBER_NICKNAME"; public static final String DEVDEVDEV_MEMBER_EMAIL = "DEVDEVDEV_MEMBER_EMAIL"; + public static final String DEVDEVDEV_MEMBER_IS_ADMIN = "DEVDEVDEV_MEMBER_IS_ADMIN"; } diff --git a/src/main/java/com/dreamypatisiel/devdevdev/global/security/jwt/model/TokenExpireTime.java b/src/main/java/com/dreamypatisiel/devdevdev/global/security/jwt/model/TokenExpireTime.java index 210b06ee..c3f5b6ab 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/global/security/jwt/model/TokenExpireTime.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/global/security/jwt/model/TokenExpireTime.java @@ -3,4 +3,5 @@ public class TokenExpireTime { public static final long ACCESS_TOKEN_EXPIRE_TIME = 1_000 * 60 * 30; // 30분 public static final long REFRESH_TOKEN_EXPIRE_TIME = 1_000 * 60 * 60 * 24 * 7; // 7일 + public static final long TEST_TOKEN_EXPIRE_TIME = 1_000 * 60 * 60 * 24 * 21; // 21일 } diff --git a/src/main/java/com/dreamypatisiel/devdevdev/global/utils/CookieUtils.java b/src/main/java/com/dreamypatisiel/devdevdev/global/utils/CookieUtils.java index f8199ad2..f048e0d9 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/global/utils/CookieUtils.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/global/utils/CookieUtils.java @@ -102,6 +102,8 @@ public static void configMemberCookie(HttpServletResponse response, Member membe nickname, DEFAULT_MAX_AGE, false, true); addCookieToResponse(response, JwtCookieConstant.DEVDEVDEV_MEMBER_EMAIL, member.getEmailAsString(), DEFAULT_MAX_AGE, false, true); + addCookieToResponse(response, JwtCookieConstant.DEVDEVDEV_MEMBER_IS_ADMIN, + String.valueOf(member.isAdmin()), DEFAULT_MAX_AGE, false, true); } private static void validationCookieEmpty(Cookie[] cookies) { diff --git a/src/main/java/com/dreamypatisiel/devdevdev/web/controller/member/MypageController.java b/src/main/java/com/dreamypatisiel/devdevdev/web/controller/member/MypageController.java index 9b73db4c..655def08 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/web/controller/member/MypageController.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/web/controller/member/MypageController.java @@ -2,18 +2,21 @@ import com.dreamypatisiel.devdevdev.domain.repository.techArticle.BookmarkSort; import com.dreamypatisiel.devdevdev.domain.service.member.MemberService; -import com.dreamypatisiel.devdevdev.domain.service.techArticle.TechArticleServiceStrategy; import com.dreamypatisiel.devdevdev.global.security.jwt.model.JwtCookieConstant; import com.dreamypatisiel.devdevdev.global.utils.AuthenticationMemberUtils; import com.dreamypatisiel.devdevdev.global.utils.CookieUtils; +import com.dreamypatisiel.devdevdev.web.dto.SliceCustom; +import com.dreamypatisiel.devdevdev.web.dto.request.comment.MyWrittenCommentRequest; import com.dreamypatisiel.devdevdev.web.dto.request.member.RecordMemberExitSurveyAnswerRequest; import com.dreamypatisiel.devdevdev.web.dto.response.BasicResponse; +import com.dreamypatisiel.devdevdev.web.dto.response.comment.MyWrittenCommentResponse; import com.dreamypatisiel.devdevdev.web.dto.response.member.MemberExitSurveyResponse; import com.dreamypatisiel.devdevdev.web.dto.response.pick.MyPickMainResponse; import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.TechArticleMainResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; @@ -24,6 +27,7 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -36,7 +40,6 @@ @RequiredArgsConstructor public class MypageController { - private final TechArticleServiceStrategy techArticleServiceStrategy; private final MemberService memberService; @Operation(summary = "북마크 목록 조회") @@ -101,4 +104,18 @@ public ResponseEntity> recordMemberExitSurvey(@RequestBody @ return ResponseEntity.ok(BasicResponse.success()); } + + @Operation(summary = "내가 작성한 댓글 조회", description = "본인이 작성한 댓글을 무한 스크롤 방식으로 조회합니다.") + @GetMapping("/mypage/comments") + public ResponseEntity>> getMyWrittenComments( + @PageableDefault(size = 6) Pageable pageable, + @Valid @ModelAttribute MyWrittenCommentRequest myWrittenCommentRequest) { + + Authentication authentication = AuthenticationMemberUtils.getAuthentication(); + + SliceCustom myWrittenComments = memberService.findMyWrittenComments(pageable, + myWrittenCommentRequest, authentication); + + return ResponseEntity.ok(BasicResponse.success(myWrittenComments)); + } } diff --git a/src/main/java/com/dreamypatisiel/devdevdev/web/controller/techArticle/TechArticleController.java b/src/main/java/com/dreamypatisiel/devdevdev/web/controller/techArticle/TechArticleController.java index 72e5ac10..b3749ef3 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/web/controller/techArticle/TechArticleController.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/web/controller/techArticle/TechArticleController.java @@ -8,6 +8,7 @@ import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.BookmarkResponse; import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.TechArticleDetailResponse; import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.TechArticleMainResponse; +import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.TechArticleRecommendResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; @@ -16,12 +17,9 @@ import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; + +import static com.dreamypatisiel.devdevdev.web.WebConstant.HEADER_ANONYMOUS_MEMBER_ID; @Tag(name = "기술블로그 API", description = "기술블로그 메인, 상세 API") @RestController @@ -51,11 +49,13 @@ public ResponseEntity>> getTechArti @Operation(summary = "기술블로그 상세 조회") @GetMapping("/articles/{techArticleId}") - public ResponseEntity> getTechArticle(@PathVariable Long techArticleId) { + public ResponseEntity> getTechArticle( + @PathVariable Long techArticleId, + @RequestHeader(value = HEADER_ANONYMOUS_MEMBER_ID, required = false) String anonymousMemberId) { TechArticleService techArticleService = techArticleServiceStrategy.getTechArticleService(); Authentication authentication = AuthenticationMemberUtils.getAuthentication(); - TechArticleDetailResponse response = techArticleService.getTechArticle(techArticleId, authentication); + TechArticleDetailResponse response = techArticleService.getTechArticle(techArticleId, anonymousMemberId, authentication); return ResponseEntity.ok(BasicResponse.success(response)); } @@ -70,4 +70,18 @@ public ResponseEntity> updateBookmark(@PathVaria return ResponseEntity.ok(BasicResponse.success(response)); } + + @Operation(summary = "기술블로그 추천") + @PostMapping("/articles/{techArticleId}/recommend") + public ResponseEntity> updateRecommend( + @PathVariable Long techArticleId, + @RequestHeader(value = HEADER_ANONYMOUS_MEMBER_ID, required = false) String anonymousMemberId + ) { + + TechArticleService techArticleService = techArticleServiceStrategy.getTechArticleService(); + Authentication authentication = AuthenticationMemberUtils.getAuthentication(); + TechArticleRecommendResponse response = techArticleService.updateRecommend(techArticleId, anonymousMemberId, authentication); + + return ResponseEntity.ok(BasicResponse.success(response)); + } } diff --git a/src/main/java/com/dreamypatisiel/devdevdev/web/dto/request/comment/MyWrittenCommentFilter.java b/src/main/java/com/dreamypatisiel/devdevdev/web/dto/request/comment/MyWrittenCommentFilter.java new file mode 100644 index 00000000..b99149c7 --- /dev/null +++ b/src/main/java/com/dreamypatisiel/devdevdev/web/dto/request/comment/MyWrittenCommentFilter.java @@ -0,0 +1,7 @@ +package com.dreamypatisiel.devdevdev.web.dto.request.comment; + +public enum MyWrittenCommentFilter { + ALL, + PICK, + TECH_ARTICLE +} diff --git a/src/main/java/com/dreamypatisiel/devdevdev/web/dto/request/comment/MyWrittenCommentRequest.java b/src/main/java/com/dreamypatisiel/devdevdev/web/dto/request/comment/MyWrittenCommentRequest.java new file mode 100644 index 00000000..16127f30 --- /dev/null +++ b/src/main/java/com/dreamypatisiel/devdevdev/web/dto/request/comment/MyWrittenCommentRequest.java @@ -0,0 +1,18 @@ +package com.dreamypatisiel.devdevdev.web.dto.request.comment; + +import java.util.Objects; +import lombok.Data; + +@Data +public class MyWrittenCommentRequest { + private Long pickCommentId; + private Long techCommentId; + private MyWrittenCommentFilter commentFilter; + + public MyWrittenCommentRequest(Long pickCommentId, Long techCommentId, + MyWrittenCommentFilter myWrittenCommentSort) { + this.pickCommentId = Objects.requireNonNullElse(pickCommentId, Long.MAX_VALUE); + this.techCommentId = Objects.requireNonNullElse(techCommentId, Long.MAX_VALUE); + this.commentFilter = Objects.requireNonNullElse(myWrittenCommentSort, MyWrittenCommentFilter.ALL); + } +} diff --git a/src/main/java/com/dreamypatisiel/devdevdev/web/dto/response/comment/MyWrittenCommentResponse.java b/src/main/java/com/dreamypatisiel/devdevdev/web/dto/response/comment/MyWrittenCommentResponse.java new file mode 100644 index 00000000..83204bbc --- /dev/null +++ b/src/main/java/com/dreamypatisiel/devdevdev/web/dto/response/comment/MyWrittenCommentResponse.java @@ -0,0 +1,73 @@ +package com.dreamypatisiel.devdevdev.web.dto.response.comment; + +import com.dreamypatisiel.devdevdev.domain.repository.comment.MyWrittenCommentDto; +import com.dreamypatisiel.devdevdev.global.common.TimeProvider; +import com.dreamypatisiel.devdevdev.web.dto.util.CommentResponseUtil; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonFormat.Shape; +import java.time.LocalDateTime; +import java.util.List; +import javax.annotation.Nullable; +import lombok.Builder; +import lombok.Data; + +@Data +public class MyWrittenCommentResponse { + private String uniqueCommentId; + private Long postId; + private String postTitle; + private Long commentId; + private String commentType; + private String commentContents; + private Long commentRecommendTotalCount; + + @JsonFormat(shape = Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = TimeProvider.DEFAULT_ZONE_ID) + private LocalDateTime commentCreatedAt; + + private String pickOptionTitle; + private String pickOptionType; + + @Builder + public MyWrittenCommentResponse(String uniqueCommentId, Long postId, String postTitle, + Long commentId, String commentType, + String commentContents, Long commentRecommendTotalCount, + LocalDateTime commentCreatedAt, @Nullable String pickOptionTitle, + @Nullable String pickOptionType) { + this.uniqueCommentId = uniqueCommentId; + this.postId = postId; + this.postTitle = postTitle; + this.commentId = commentId; + this.commentType = commentType; + this.commentContents = commentContents; + this.commentRecommendTotalCount = commentRecommendTotalCount; + this.commentCreatedAt = commentCreatedAt; + this.pickOptionTitle = pickOptionTitle; + this.pickOptionType = pickOptionType; + } + + public static MyWrittenCommentResponse from(MyWrittenCommentDto myWrittenCommentDto) { + + Long postId = myWrittenCommentDto.getPostId(); + Long commentId = myWrittenCommentDto.getCommentId(); + String commentType = myWrittenCommentDto.getCommentType(); + + return MyWrittenCommentResponse.builder() + .uniqueCommentId(CommentResponseUtil.createUniqueCommentId(commentType, postId, commentId)) + .postId(postId) + .postTitle(myWrittenCommentDto.getPostTitle()) + .commentId(commentId) + .commentType(commentType) + .commentContents(myWrittenCommentDto.getCommentContents()) + .commentRecommendTotalCount(myWrittenCommentDto.getCommentRecommendTotalCount()) + .commentCreatedAt(myWrittenCommentDto.getCommentCreatedAt()) + .pickOptionTitle(myWrittenCommentDto.getPickOptionTitle()) + .pickOptionType(myWrittenCommentDto.getPickOptionType()) + .build(); + } + + public static List from(List myWrittenCommentsDto) { + return myWrittenCommentsDto.stream() + .map(MyWrittenCommentResponse::from) + .toList(); + } +} diff --git a/src/main/java/com/dreamypatisiel/devdevdev/web/dto/response/member/MemberExitSurveyAnswerResponse.java b/src/main/java/com/dreamypatisiel/devdevdev/web/dto/response/member/MemberExitSurveyAnswerResponse.java deleted file mode 100644 index f6f50ecf..00000000 --- a/src/main/java/com/dreamypatisiel/devdevdev/web/dto/response/member/MemberExitSurveyAnswerResponse.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.dreamypatisiel.devdevdev.web.dto.response.member; - -import java.util.List; -import lombok.Data; - -@Data -public class MemberExitSurveyAnswerResponse { - private final List id; - - public MemberExitSurveyAnswerResponse(List id) { - this.id = id; - } -} diff --git a/src/main/java/com/dreamypatisiel/devdevdev/web/dto/response/techArticle/TechArticleDetailResponse.java b/src/main/java/com/dreamypatisiel/devdevdev/web/dto/response/techArticle/TechArticleDetailResponse.java index 4a52a35f..bcf0f84d 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/web/dto/response/techArticle/TechArticleDetailResponse.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/web/dto/response/techArticle/TechArticleDetailResponse.java @@ -1,7 +1,6 @@ package com.dreamypatisiel.devdevdev.web.dto.response.techArticle; -import static com.dreamypatisiel.devdevdev.web.dto.util.TechArticleResponseUtils.isBookmarkedByMember; - +import com.dreamypatisiel.devdevdev.domain.entity.AnonymousMember; import com.dreamypatisiel.devdevdev.domain.entity.Member; import com.dreamypatisiel.devdevdev.domain.entity.TechArticle; import com.dreamypatisiel.devdevdev.elastic.domain.document.ElasticTechArticle; @@ -9,6 +8,8 @@ import lombok.Builder; import lombok.Data; +import static com.dreamypatisiel.devdevdev.web.dto.util.TechArticleResponseUtils.*; + @Data public class TechArticleDetailResponse { @@ -27,13 +28,13 @@ public class TechArticleDetailResponse { public final Long commentTotalCount; public final Long popularScore; public final Boolean isBookmarked; + public final Boolean isRecommended; @Builder private TechArticleDetailResponse(String elasticId, String thumbnailUrl, String techArticleUrl, String title, - String contents, - CompanyResponse company, LocalDate regDate, String author, Long viewTotalCount, - Long recommendTotalCount, Long commentTotalCount, Long popularScore, - Boolean isBookmarked) { + String contents, CompanyResponse company, LocalDate regDate, String author, + Long viewTotalCount, Long recommendTotalCount, Long commentTotalCount, Long popularScore, + Boolean isBookmarked, Boolean isRecommended) { this.elasticId = elasticId; this.thumbnailUrl = thumbnailUrl; this.techArticleUrl = techArticleUrl; @@ -47,11 +48,13 @@ private TechArticleDetailResponse(String elasticId, String thumbnailUrl, String this.recommendTotalCount = recommendTotalCount; this.commentTotalCount = commentTotalCount; this.popularScore = popularScore; + this.isRecommended = isRecommended; } public static TechArticleDetailResponse of(TechArticle techArticle, ElasticTechArticle elasticTechArticle, - CompanyResponse companyResponse) { + CompanyResponse companyResponse, + AnonymousMember anonymousMember) { return TechArticleDetailResponse.builder() .viewTotalCount(techArticle.getViewTotalCount().getCount()) .recommendTotalCount(techArticle.getRecommendTotalCount().getCount()) @@ -66,6 +69,7 @@ public static TechArticleDetailResponse of(TechArticle techArticle, .author(elasticTechArticle.getAuthor()) .contents(truncateString(elasticTechArticle.getContents(), CONTENTS_MAX_LENGTH)) .isBookmarked(false) + .isRecommended(isRecommendedByAnonymousMember(techArticle, anonymousMember)) .build(); } @@ -87,6 +91,7 @@ public static TechArticleDetailResponse of(TechArticle techArticle, .author(elasticTechArticle.getAuthor()) .contents(truncateString(elasticTechArticle.getContents(), CONTENTS_MAX_LENGTH)) .isBookmarked(isBookmarkedByMember(techArticle, member)) + .isRecommended(isRecommendedByMember(techArticle, member)) .build(); } diff --git a/src/main/java/com/dreamypatisiel/devdevdev/web/dto/response/techArticle/TechArticleRecommendResponse.java b/src/main/java/com/dreamypatisiel/devdevdev/web/dto/response/techArticle/TechArticleRecommendResponse.java new file mode 100644 index 00000000..87d35e02 --- /dev/null +++ b/src/main/java/com/dreamypatisiel/devdevdev/web/dto/response/techArticle/TechArticleRecommendResponse.java @@ -0,0 +1,17 @@ +package com.dreamypatisiel.devdevdev.web.dto.response.techArticle; + +import lombok.Data; + +@Data +public class TechArticleRecommendResponse { + + public final Long techArticleId; + public final Boolean status; + public final Long recommendTotalCount; + + public TechArticleRecommendResponse(Long techArticleId, Boolean status, Long recommendTotalCount) { + this.techArticleId = techArticleId; + this.status = status; + this.recommendTotalCount = recommendTotalCount; + } +} diff --git a/src/main/java/com/dreamypatisiel/devdevdev/web/dto/util/CommentResponseUtil.java b/src/main/java/com/dreamypatisiel/devdevdev/web/dto/util/CommentResponseUtil.java index 001d2ad6..e8c66ec1 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/web/dto/util/CommentResponseUtil.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/web/dto/util/CommentResponseUtil.java @@ -10,6 +10,7 @@ import javax.annotation.Nullable; public class CommentResponseUtil { + public static String getCommentByPickCommentStatus(PickComment pickComment) { if (pickComment.isDeleted()) { // 댓글 작성자에 의해 삭제된 경우 @@ -87,4 +88,8 @@ public static boolean isTechCommentRecommendedByMember(@Nullable Member member, return recommends.map(TechCommentRecommend::isRecommended).orElse(false); } + + public static String createUniqueCommentId(String commentType, Long postId, Long commentId) { + return commentType + "_" + postId + "_" + commentId; + } } diff --git a/src/main/java/com/dreamypatisiel/devdevdev/web/dto/util/TechArticleResponseUtils.java b/src/main/java/com/dreamypatisiel/devdevdev/web/dto/util/TechArticleResponseUtils.java index 5dcfb43a..cd54ef51 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/web/dto/util/TechArticleResponseUtils.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/web/dto/util/TechArticleResponseUtils.java @@ -1,8 +1,6 @@ package com.dreamypatisiel.devdevdev.web.dto.util; -import com.dreamypatisiel.devdevdev.domain.entity.Bookmark; -import com.dreamypatisiel.devdevdev.domain.entity.Member; -import com.dreamypatisiel.devdevdev.domain.entity.TechArticle; +import com.dreamypatisiel.devdevdev.domain.entity.*; import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.TechArticleMainResponse; import java.util.List; import java.util.Optional; @@ -18,6 +16,30 @@ public static boolean isBookmarkedByMember(TechArticle techArticle, Member membe return bookmarks.map(Bookmark::isBookmarked).orElse(false); } + public static boolean isRecommendedByMember(TechArticle techArticle, Member member) { + Optional recommends = techArticle.getRecommends().stream() + .filter(recommend -> + recommend.isMemberNotNull() + && recommend.getMember().isEqualsId(member.getId())) + .findAny(); + + return recommends.map(TechArticleRecommend::isRecommended).orElse(false); + } + + public static boolean isRecommendedByAnonymousMember(TechArticle techArticle, AnonymousMember anonymousMember) { + if(anonymousMember == null) { + return false; + } + + Optional recommends = techArticle.getRecommends().stream() + .filter(recommend -> + recommend.isAnonymousMemberNotNull() + && recommend.getAnonymousMember().isEqualAnonymousMemberId(anonymousMember.getId())) + .findAny(); + + return recommends.map(TechArticleRecommend::isRecommended).orElse(false); + } + public static boolean hasNextPage(List contents, Pageable pageable) { return contents.size() >= pageable.getPageSize(); } diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 61b6f2ef..1f004d1b 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -61,6 +61,14 @@ spring: max-idle: 8 min-idle: 2 +#MyBatis +mybatis: + type-aliases-package: com.dreamypatisiel.devdevdev.domain.repository + configuration: + map-underscore-to-camel-case: true + mapper-locations: classpath:mapper/**/*.xml + logging: level: - org.hibernate.SQL: DEBUG \ No newline at end of file + org.hibernate.SQL: DEBUG + com.dreamypatisiel.devdevdev: TRACE diff --git a/src/main/resources/mapper/commment/Comment.xml b/src/main/resources/mapper/commment/Comment.xml new file mode 100644 index 00000000..19cff929 --- /dev/null +++ b/src/main/resources/mapper/commment/Comment.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/com/dreamypatisiel/devdevdev/domain/repository/comment/CommentRepositoryTest.java b/src/test/java/com/dreamypatisiel/devdevdev/domain/repository/comment/CommentRepositoryTest.java new file mode 100644 index 00000000..73769021 --- /dev/null +++ b/src/test/java/com/dreamypatisiel/devdevdev/domain/repository/comment/CommentRepositoryTest.java @@ -0,0 +1,260 @@ +package com.dreamypatisiel.devdevdev.domain.repository.comment; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +import com.dreamypatisiel.devdevdev.domain.entity.Company; +import com.dreamypatisiel.devdevdev.domain.entity.Member; +import com.dreamypatisiel.devdevdev.domain.entity.Pick; +import com.dreamypatisiel.devdevdev.domain.entity.PickComment; +import com.dreamypatisiel.devdevdev.domain.entity.PickOption; +import com.dreamypatisiel.devdevdev.domain.entity.PickVote; +import com.dreamypatisiel.devdevdev.domain.entity.TechArticle; +import com.dreamypatisiel.devdevdev.domain.entity.TechComment; +import com.dreamypatisiel.devdevdev.domain.entity.embedded.CommentContents; +import com.dreamypatisiel.devdevdev.domain.entity.embedded.CompanyName; +import com.dreamypatisiel.devdevdev.domain.entity.embedded.Title; +import com.dreamypatisiel.devdevdev.domain.entity.enums.PickOptionType; +import com.dreamypatisiel.devdevdev.domain.entity.enums.Role; +import com.dreamypatisiel.devdevdev.domain.entity.enums.SocialType; +import com.dreamypatisiel.devdevdev.domain.repository.CompanyRepository; +import com.dreamypatisiel.devdevdev.domain.repository.comment.mybatis.CommentMapper; +import com.dreamypatisiel.devdevdev.domain.repository.member.MemberRepository; +import com.dreamypatisiel.devdevdev.domain.repository.pick.PickCommentRepository; +import com.dreamypatisiel.devdevdev.domain.repository.pick.PickOptionRepository; +import com.dreamypatisiel.devdevdev.domain.repository.pick.PickRepository; +import com.dreamypatisiel.devdevdev.domain.repository.pick.PickVoteRepository; +import com.dreamypatisiel.devdevdev.domain.repository.techArticle.TechArticleRepository; +import com.dreamypatisiel.devdevdev.domain.repository.techArticle.TechCommentRepository; +import com.dreamypatisiel.devdevdev.global.security.oauth2.model.SocialMemberDto; +import com.dreamypatisiel.devdevdev.web.dto.SliceCustom; +import com.dreamypatisiel.devdevdev.web.dto.request.comment.MyWrittenCommentFilter; +import jakarta.transaction.Transactional; +import java.time.LocalDateTime; +import java.util.Optional; +import org.assertj.core.groups.Tuple; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.MockitoAnnotations; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.data.auditing.AuditingHandler; +import org.springframework.data.auditing.DateTimeProvider; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +@SpringBootTest +@Transactional +class CommentRepositoryTest { + + @Autowired + CommentRepository commentRepository; + @Autowired + CommentMapper commentMapper; + @Autowired + MemberRepository memberRepository; + @Autowired + PickRepository pickRepository; + @Autowired + PickCommentRepository pickCommentRepository; + @Autowired + PickVoteRepository pickVoteRepository; + @Autowired + PickOptionRepository pickOptionRepository; + @Autowired + CompanyRepository companyRepository; + @Autowired + TechArticleRepository techArticleRepository; + @Autowired + TechCommentRepository techCommentRepository; + + @MockBean + DateTimeProvider dateTimeProvider; + + @SpyBean + AuditingHandler auditingHandler; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + auditingHandler.setDateTimeProvider(dateTimeProvider); + } + + @Test + @DisplayName("회원이 작성하고 삭제상태가 아닌 픽픽픽, 기술블로그 댓글을 작성시간 내림차순으로 조회한다.") + void findMyWrittenCommentsByCursorCursor() { + // given + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 1, 1, 0, 0))); + + // 회원 생성 + SocialMemberDto socialMemberDto = createSocialDto("user", "name", "nickname", "password", "user@gmail.com", + SocialType.KAKAO.name(), Role.ROLE_USER.name()); + Member member = Member.createMemberBy(socialMemberDto); + memberRepository.save(member); + + // 픽픽픽 생성 + Pick pick = createPick("픽픽픽", member); + pickRepository.save(pick); + + // 픽픽픽 옵션 생성 + PickOption pickOption = createPickOption(pick, "픽픽픽 A", PickOptionType.firstPickOption); + pickOptionRepository.save(pickOption); + + // 픽픽픽 투표 생성 + PickVote pickVote = createPickVote(pick, pickOption, member); + pickVoteRepository.save(pickVote); + + // 픽픽픽 댓글 생성 + PickComment pickComment1 = createPickComment(pick, member, pickVote, null, null, "픽픽픽 댓글1", true); + PickComment pickComment2 = createPickComment(pick, member, null, pickComment1, pickComment1, "픽픽픽 댓글2", false); + PickComment pickComment3 = createPickComment(pick, member, null, pickComment1, pickComment2, "픽픽픽 댓글3", false); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 1, 1, 0, 0))); + pickCommentRepository.save(pickComment1); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 2, 1, 0, 0))); + pickCommentRepository.save(pickComment2); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 3, 1, 0, 0))); + pickCommentRepository.save(pickComment3); + + // 기술블로그 회사 생성 + Company company = createCompany("DreamyPatisiel"); + companyRepository.save(company); + + // 기술블로그 생성 + TechArticle techArticle = createTechArticle(company, "기술블로그 제목"); + techArticleRepository.save(techArticle); + + // 기술블로그 댓글 생성 + TechComment techComment1 = createTechComment(techArticle, member, null, null, "기술블로그 댓글1"); + TechComment techComment2 = createTechComment(techArticle, member, techComment1, techComment1, "기술블로그 댓글2"); + TechComment techComment3 = createTechComment(techArticle, member, techComment1, techComment2, "기술블로그 댓글3"); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 4, 1, 0, 0))); + techCommentRepository.save(techComment1); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 5, 1, 0, 0))); + techCommentRepository.save(techComment2); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 6, 1, 0, 0))); + techCommentRepository.save(techComment3); + + // when + Long techCommentId = techComment3.getId() + 1L; + Long pickCommentId = pickComment3.getId() + 1L; + Pageable pageable = PageRequest.of(0, 6); + + // when + SliceCustom findMyWrittenComments = commentRepository.findMyWrittenCommentsByCursor( + member.getId(), pickCommentId, techCommentId, MyWrittenCommentFilter.ALL, pageable); + + // then + assertThat(findMyWrittenComments.getTotalElements()).isEqualTo(6); + assertThat(findMyWrittenComments.getContent()).hasSize(6) + .extracting("postId", "postTitle", "commentId", "commentType", "commentContents", + "pickOptionTitle", "pickOptionType", "commentCreatedAt") + .containsExactly( + Tuple.tuple(techArticle.getId(), techArticle.getTitle().getTitle(), techComment3.getId(), + "TECH_ARTICLE", techComment3.getContents().getCommentContents(), + null, null, + techComment3.getCreatedAt()), + Tuple.tuple(techArticle.getId(), techArticle.getTitle().getTitle(), techComment2.getId(), + "TECH_ARTICLE", techComment2.getContents().getCommentContents(), + null, null, + techComment2.getCreatedAt()), + Tuple.tuple(techArticle.getId(), techArticle.getTitle().getTitle(), techComment1.getId(), + "TECH_ARTICLE", techComment1.getContents().getCommentContents(), + null, null, + techComment1.getCreatedAt()), + Tuple.tuple(pick.getId(), pick.getTitle().getTitle(), pickComment3.getId(), + "PICK", pickComment3.getContents().getCommentContents(), + null, null, + pickComment3.getCreatedAt()), + Tuple.tuple(pick.getId(), pick.getTitle().getTitle(), pickComment2.getId(), + "PICK", pickComment2.getContents().getCommentContents(), + null, null, + pickComment2.getCreatedAt()), + Tuple.tuple(pick.getId(), pick.getTitle().getTitle(), pickComment1.getId(), + "PICK", pickComment1.getContents().getCommentContents(), + pickOption.getTitle().getTitle(), pickOption.getPickOptionType().name(), + pickComment1.getCreatedAt()) + ); + } + + private static TechComment createTechComment(TechArticle techArticle, Member member, TechComment originParent, + TechComment parent, String contents) { + return TechComment.builder() + .techArticle(techArticle) + .createdBy(member) + .originParent(originParent) + .parent(parent) + .contents(new CommentContents(contents)) + .build(); + } + + private static TechArticle createTechArticle(Company company, String title) { + return TechArticle.builder() + .company(company) + .title(new Title(title)) + .build(); + } + + private static Company createCompany(String companyName) { + return Company.builder() + .name(new CompanyName(companyName)) + .build(); + } + + private static PickOption createPickOption(Pick pick, String title, PickOptionType pickOptionType) { + return PickOption.builder() + .pick(pick) + .title(new Title(title)) + .pickOptionType(pickOptionType) + .build(); + } + + private static PickVote createPickVote(Pick pick, PickOption pickOption, Member member) { + return PickVote.builder() + .pick(pick) + .pickOption(pickOption) + .member(member) + .build(); + } + + private static PickComment createPickComment(Pick pick, Member member, PickVote pickVote, PickComment originParent, + PickComment parent, String contents, Boolean isPublic) { + return PickComment.builder() + .pick(pick) + .createdBy(member) + .pickVote(pickVote) + .originParent(originParent) + .parent(parent) + .contents(new CommentContents(contents)) + .isPublic(isPublic) + .build(); + } + + private static Pick createPick(String title, Member member) { + return Pick.builder() + .title(new Title(title)) + .member(member) + .build(); + } + + private SocialMemberDto createSocialDto(String userId, String name, String nickName, String password, String email, + String socialType, String role) { + return SocialMemberDto.builder() + .userId(userId) + .name(name) + .nickname(nickName) + .password(password) + .email(email) + .socialType(SocialType.valueOf(socialType)) + .role(Role.valueOf(role)) + .build(); + } +} \ No newline at end of file diff --git a/src/test/java/com/dreamypatisiel/devdevdev/domain/repository/comment/mybatis/CommentMapperTest.java b/src/test/java/com/dreamypatisiel/devdevdev/domain/repository/comment/mybatis/CommentMapperTest.java new file mode 100644 index 00000000..4fcdf8ea --- /dev/null +++ b/src/test/java/com/dreamypatisiel/devdevdev/domain/repository/comment/mybatis/CommentMapperTest.java @@ -0,0 +1,254 @@ +package com.dreamypatisiel.devdevdev.domain.repository.comment.mybatis; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +import com.dreamypatisiel.devdevdev.domain.entity.Company; +import com.dreamypatisiel.devdevdev.domain.entity.Member; +import com.dreamypatisiel.devdevdev.domain.entity.Pick; +import com.dreamypatisiel.devdevdev.domain.entity.PickComment; +import com.dreamypatisiel.devdevdev.domain.entity.PickOption; +import com.dreamypatisiel.devdevdev.domain.entity.PickVote; +import com.dreamypatisiel.devdevdev.domain.entity.TechArticle; +import com.dreamypatisiel.devdevdev.domain.entity.TechComment; +import com.dreamypatisiel.devdevdev.domain.entity.embedded.CommentContents; +import com.dreamypatisiel.devdevdev.domain.entity.embedded.CompanyName; +import com.dreamypatisiel.devdevdev.domain.entity.embedded.Title; +import com.dreamypatisiel.devdevdev.domain.entity.enums.PickOptionType; +import com.dreamypatisiel.devdevdev.domain.entity.enums.Role; +import com.dreamypatisiel.devdevdev.domain.entity.enums.SocialType; +import com.dreamypatisiel.devdevdev.domain.repository.CompanyRepository; +import com.dreamypatisiel.devdevdev.domain.repository.comment.MyWrittenCommentDto; +import com.dreamypatisiel.devdevdev.domain.repository.member.MemberRepository; +import com.dreamypatisiel.devdevdev.domain.repository.pick.PickCommentRepository; +import com.dreamypatisiel.devdevdev.domain.repository.pick.PickOptionRepository; +import com.dreamypatisiel.devdevdev.domain.repository.pick.PickRepository; +import com.dreamypatisiel.devdevdev.domain.repository.pick.PickVoteRepository; +import com.dreamypatisiel.devdevdev.domain.repository.techArticle.TechArticleRepository; +import com.dreamypatisiel.devdevdev.domain.repository.techArticle.TechCommentRepository; +import com.dreamypatisiel.devdevdev.global.security.oauth2.model.SocialMemberDto; +import jakarta.transaction.Transactional; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; +import org.assertj.core.groups.Tuple; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.MockitoAnnotations; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.data.auditing.AuditingHandler; +import org.springframework.data.auditing.DateTimeProvider; + +@SpringBootTest +@Transactional +class CommentMapperTest { + + @Autowired + CommentMapper commentMapper; + @Autowired + MemberRepository memberRepository; + @Autowired + PickRepository pickRepository; + @Autowired + PickCommentRepository pickCommentRepository; + @Autowired + PickVoteRepository pickVoteRepository; + @Autowired + PickOptionRepository pickOptionRepository; + @Autowired + CompanyRepository companyRepository; + @Autowired + TechArticleRepository techArticleRepository; + @Autowired + TechCommentRepository techCommentRepository; + + @MockBean + DateTimeProvider dateTimeProvider; + + @SpyBean + AuditingHandler auditingHandler; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + auditingHandler.setDateTimeProvider(dateTimeProvider); + } + + @Test + @DisplayName("회원이 작성한 픽픽픽, 기술블로그 댓글을 커서 방식으로 작성일시 내림차순으로 조회한다.") + void findByMemberIdAndPickCommentIdAndTechCommentIdOrderByCommentCreatedAtDesc() { + // given + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 1, 1, 0, 0))); + + // 회원 생성 + SocialMemberDto socialMemberDto = createSocialDto("user", "name", "nickname", "password", "user@gmail.com", + SocialType.KAKAO.name(), Role.ROLE_USER.name()); + Member member = Member.createMemberBy(socialMemberDto); + memberRepository.save(member); + + // 픽픽픽 생성 + Pick pick = createPick("픽픽픽", member); + pickRepository.save(pick); + + // 픽픽픽 옵션 생성 + PickOption pickOption = createPickOption(pick, "픽픽픽 A", PickOptionType.firstPickOption); + pickOptionRepository.save(pickOption); + + // 픽픽픽 투표 생성 + PickVote pickVote = createPickVote(pick, pickOption, member); + pickVoteRepository.save(pickVote); + + // 픽픽픽 댓글 생성 + PickComment pickComment1 = createPickComment(pick, member, pickVote, null, null, "픽픽픽 댓글1", true); + PickComment pickComment2 = createPickComment(pick, member, null, pickComment1, pickComment1, "픽픽픽 댓글2", false); + PickComment pickComment3 = createPickComment(pick, member, null, pickComment1, pickComment2, "픽픽픽 댓글3", false); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 1, 1, 0, 0))); + pickCommentRepository.save(pickComment1); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 2, 1, 0, 0))); + pickCommentRepository.save(pickComment2); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 3, 1, 0, 0))); + pickCommentRepository.save(pickComment3); + + // 기술블로그 회사 생성 + Company company = createCompany("DreamyPatisiel"); + companyRepository.save(company); + + // 기술블로그 생성 + TechArticle techArticle = createTechArticle(company, "기술블로그 제목"); + techArticleRepository.save(techArticle); + + // 기술블로그 댓글 생성 + TechComment techComment1 = createTechComment(techArticle, member, null, null, "기술블로그 댓글1"); + TechComment techComment2 = createTechComment(techArticle, member, techComment1, techComment1, "기술블로그 댓글2"); + TechComment techComment3 = createTechComment(techArticle, member, techComment1, techComment2, "기술블로그 댓글3"); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 4, 1, 0, 0))); + techCommentRepository.save(techComment1); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 5, 1, 0, 0))); + techCommentRepository.save(techComment2); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 6, 1, 0, 0))); + techCommentRepository.save(techComment3); + + // when + Long techCommentId = techComment3.getId() + 1L; + Long pickCommentId = pickComment3.getId() + 1L; + + int limit = 6; + + List myWrittenComments = commentMapper.findByMemberIdAndPickCommentIdAndTechCommentIdOrderByCommentCreatedAtDesc( + member.getId(), pickCommentId, techCommentId, limit); + + // then + assertThat(myWrittenComments).hasSize(6) + .extracting("postId", "postTitle", "commentId", "commentType", "commentContents", + "pickOptionTitle", "pickOptionType", "commentCreatedAt") + .containsExactly( + Tuple.tuple(techArticle.getId(), techArticle.getTitle().getTitle(), techComment3.getId(), + "TECH_ARTICLE", techComment3.getContents().getCommentContents(), + null, null, + techComment3.getCreatedAt()), + Tuple.tuple(techArticle.getId(), techArticle.getTitle().getTitle(), techComment2.getId(), + "TECH_ARTICLE", techComment2.getContents().getCommentContents(), + null, null, + techComment2.getCreatedAt()), + Tuple.tuple(techArticle.getId(), techArticle.getTitle().getTitle(), techComment1.getId(), + "TECH_ARTICLE", techComment1.getContents().getCommentContents(), + null, null, + techComment1.getCreatedAt()), + Tuple.tuple(pick.getId(), pick.getTitle().getTitle(), pickComment3.getId(), + "PICK", pickComment3.getContents().getCommentContents(), + null, null, + pickComment3.getCreatedAt()), + Tuple.tuple(pick.getId(), pick.getTitle().getTitle(), pickComment2.getId(), + "PICK", pickComment2.getContents().getCommentContents(), + null, null, + pickComment2.getCreatedAt()), + Tuple.tuple(pick.getId(), pick.getTitle().getTitle(), pickComment1.getId(), + "PICK", pickComment1.getContents().getCommentContents(), + pickOption.getTitle().getTitle(), pickOption.getPickOptionType().name(), + pickComment1.getCreatedAt()) + ); + } + + private static TechComment createTechComment(TechArticle techArticle, Member member, TechComment originParent, + TechComment parent, String contents) { + return TechComment.builder() + .techArticle(techArticle) + .createdBy(member) + .originParent(originParent) + .parent(parent) + .contents(new CommentContents(contents)) + .build(); + } + + private static TechArticle createTechArticle(Company company, String title) { + return TechArticle.builder() + .company(company) + .title(new Title(title)) + .build(); + } + + private static Company createCompany(String companyName) { + return Company.builder() + .name(new CompanyName(companyName)) + .build(); + } + + private static PickOption createPickOption(Pick pick, String title, PickOptionType pickOptionType) { + return PickOption.builder() + .pick(pick) + .title(new Title(title)) + .pickOptionType(pickOptionType) + .build(); + } + + private static PickVote createPickVote(Pick pick, PickOption pickOption, Member member) { + return PickVote.builder() + .pick(pick) + .pickOption(pickOption) + .member(member) + .build(); + } + + private static PickComment createPickComment(Pick pick, Member member, PickVote pickVote, PickComment originParent, + PickComment parent, String contents, Boolean isPublic) { + return PickComment.builder() + .pick(pick) + .createdBy(member) + .pickVote(pickVote) + .originParent(originParent) + .parent(parent) + .contents(new CommentContents(contents)) + .isPublic(isPublic) + .build(); + } + + private static Pick createPick(String title, Member member) { + return Pick.builder() + .title(new Title(title)) + .member(member) + .build(); + } + + private SocialMemberDto createSocialDto(String userId, String name, String nickName, String password, String email, + String socialType, String role) { + return SocialMemberDto.builder() + .userId(userId) + .name(name) + .nickname(nickName) + .password(password) + .email(email) + .socialType(SocialType.valueOf(socialType)) + .role(Role.valueOf(role)) + .build(); + } +} \ No newline at end of file diff --git a/src/test/java/com/dreamypatisiel/devdevdev/domain/repository/pick/PickRepositoryTest.java b/src/test/java/com/dreamypatisiel/devdevdev/domain/repository/pick/PickRepositoryTest.java index dd937d77..295a43fd 100644 --- a/src/test/java/com/dreamypatisiel/devdevdev/domain/repository/pick/PickRepositoryTest.java +++ b/src/test/java/com/dreamypatisiel/devdevdev/domain/repository/pick/PickRepositoryTest.java @@ -3,6 +3,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; +import com.dreamypatisiel.devdevdev.domain.entity.Member; import com.dreamypatisiel.devdevdev.domain.entity.Pick; import com.dreamypatisiel.devdevdev.domain.entity.PickOption; import com.dreamypatisiel.devdevdev.domain.entity.PickOptionImage; @@ -49,6 +50,9 @@ class PickRepositoryTest { @DisplayName("findPicksByCursor 쿼리 확인") void findPicksByCursor() { // given + Member member = createMember("사용자"); + memberRepository.save(member); + PickOption pickOption1 = createPickOption(new Title("픽옵션1"), new PickOptionContents("픽콘텐츠1"), new Count(1), PickOptionType.firstPickOption); PickOption pickOption2 = createPickOption(new Title("픽옵션2"), new PickOptionContents("픽콘텐츠2"), new Count(2), @@ -66,19 +70,26 @@ void findPicksByCursor() { Count pick2ViewTotalCount = new Count(1); Count pick1CommentTotalCount = new Count(0); Count pick2CommentTotalCount = new Count(0); - Pick pick1 = createPick(new Title("픽1타이틀"), pick1VoteTotalCount, pick1ViewTotalCount, pick1CommentTotalCount, + Pick pick1 = createPick(member, new Title("픽1타이틀"), pick1VoteTotalCount, pick1ViewTotalCount, + pick1CommentTotalCount, thumbnailUrl, author, List.of(pickOption1, pickOption2), List.of()); - Pick pick2 = createPick(new Title("픽2타이틀"), pick2VoteTotalCount, pick2ViewTotalCount, pick2CommentTotalCount, + Pick pick2 = createPick(member, new Title("픽2타이틀"), pick2VoteTotalCount, pick2ViewTotalCount, + pick2CommentTotalCount, thumbnailUrl, author, List.of(pickOption3, pickOption4), List.of()); - Pick pick3 = createPick(new Title("픽3타이틀"), pick2VoteTotalCount, pick2ViewTotalCount, pick2CommentTotalCount, + Pick pick3 = createPick(member, new Title("픽3타이틀"), pick2VoteTotalCount, pick2ViewTotalCount, + pick2CommentTotalCount, thumbnailUrl, author, List.of(pickOption3, pickOption4), List.of()); - Pick pick4 = createPick(new Title("픽4타이틀"), pick2VoteTotalCount, pick2ViewTotalCount, pick2CommentTotalCount, + Pick pick4 = createPick(member, new Title("픽4타이틀"), pick2VoteTotalCount, pick2ViewTotalCount, + pick2CommentTotalCount, thumbnailUrl, author, List.of(pickOption3, pickOption4), List.of()); - Pick pick5 = createPick(new Title("픽5타이틀"), pick2VoteTotalCount, pick2ViewTotalCount, pick2CommentTotalCount, + Pick pick5 = createPick(member, new Title("픽5타이틀"), pick2VoteTotalCount, pick2ViewTotalCount, + pick2CommentTotalCount, thumbnailUrl, author, List.of(pickOption3, pickOption4), List.of()); - Pick pick6 = createPick(new Title("픽6타이틀"), pick2VoteTotalCount, pick2ViewTotalCount, pick2CommentTotalCount, + Pick pick6 = createPick(member, new Title("픽6타이틀"), pick2VoteTotalCount, pick2ViewTotalCount, + pick2CommentTotalCount, thumbnailUrl, author, List.of(pickOption3, pickOption4), List.of()); - Pick pick7 = createPick(new Title("픽7타이틀"), pick2VoteTotalCount, pick2ViewTotalCount, pick2CommentTotalCount, + Pick pick7 = createPick(member, new Title("픽7타이틀"), pick2VoteTotalCount, pick2ViewTotalCount, + pick2CommentTotalCount, thumbnailUrl, author, List.of(pickOption3, pickOption4), List.of()); pickRepository.saveAll(List.of(pick1, pick2, pick3, pick4, pick5, pick6, pick7)); pickOptionRepository.saveAll(List.of(pickOption1, pickOption2, pickOption3, pickOption4)); @@ -96,12 +107,15 @@ void findPicksByCursor() { @DisplayName("조회수 내림차순으로 Pick을 조회한다.") void findPicksByCursorOrderByViewCountDesc() { // given - Pick pick1 = createPickViewTotalCount(new Title("픽1타이틀"), new Count(1)); - Pick pick2 = createPickViewTotalCount(new Title("픽2타이틀"), new Count(2)); - Pick pick3 = createPickViewTotalCount(new Title("픽3타이틀"), new Count(3)); - Pick pick4 = createPickViewTotalCount(new Title("픽4타이틀"), new Count(3)); - Pick pick5 = createPickViewTotalCount(new Title("픽5타이틀"), new Count(4)); - Pick pick6 = createPickViewTotalCount(new Title("픽6타이틀"), new Count(5)); + Member member = createMember("사용자"); + memberRepository.save(member); + + Pick pick1 = createPickViewTotalCount(member, new Title("픽1타이틀"), new Count(1)); + Pick pick2 = createPickViewTotalCount(member, new Title("픽2타이틀"), new Count(2)); + Pick pick3 = createPickViewTotalCount(member, new Title("픽3타이틀"), new Count(3)); + Pick pick4 = createPickViewTotalCount(member, new Title("픽4타이틀"), new Count(3)); + Pick pick5 = createPickViewTotalCount(member, new Title("픽5타이틀"), new Count(4)); + Pick pick6 = createPickViewTotalCount(member, new Title("픽6타이틀"), new Count(5)); pickRepository.saveAll(List.of(pick1, pick2, pick3, pick4, pick5, pick6)); @@ -122,12 +136,15 @@ void findPicksByCursorOrderByViewCountDesc() { @DisplayName("생성시간 내림차순으로 Pick을 조회한다.") void findPicksByCursorOrderByCreatedAtDesc() { // given - Pick pick1 = createPick(new Title("픽1타이틀")); - Pick pick2 = createPick(new Title("픽2타이틀")); - Pick pick3 = createPick(new Title("픽3타이틀")); - Pick pick4 = createPick(new Title("픽4타이틀")); - Pick pick5 = createPick(new Title("픽5타이틀")); - Pick pick6 = createPick(new Title("픽6타이틀")); + Member member = createMember("사용자"); + memberRepository.save(member); + + Pick pick1 = createPick(member, new Title("픽1타이틀")); + Pick pick2 = createPick(member, new Title("픽2타이틀")); + Pick pick3 = createPick(member, new Title("픽3타이틀")); + Pick pick4 = createPick(member, new Title("픽4타이틀")); + Pick pick5 = createPick(member, new Title("픽5타이틀")); + Pick pick6 = createPick(member, new Title("픽6타이틀")); pickRepository.saveAll(List.of(pick1, pick2, pick3, pick4, pick5, pick6)); @@ -148,12 +165,15 @@ void findPicksByCursorOrderByCreatedAtDesc() { @DisplayName("댓글수 내림차순으로 Pick을 조회한다.") void findPicksByCursorOrderByDesc() { // given - Pick pick1 = createPickCommentTotalCount(new Title("픽1타이틀"), new Count(1)); - Pick pick2 = createPickCommentTotalCount(new Title("픽2타이틀"), new Count(2)); - Pick pick3 = createPickCommentTotalCount(new Title("픽3타이틀"), new Count(3)); - Pick pick4 = createPickCommentTotalCount(new Title("픽4타이틀"), new Count(3)); - Pick pick5 = createPickCommentTotalCount(new Title("픽5타이틀"), new Count(4)); - Pick pick6 = createPickCommentTotalCount(new Title("픽6타이틀"), new Count(5)); + Member member = createMember("사용자"); + memberRepository.save(member); + + Pick pick1 = createPickCommentTotalCount(member, new Title("픽1타이틀"), new Count(1)); + Pick pick2 = createPickCommentTotalCount(member, new Title("픽2타이틀"), new Count(2)); + Pick pick3 = createPickCommentTotalCount(member, new Title("픽3타이틀"), new Count(3)); + Pick pick4 = createPickCommentTotalCount(member, new Title("픽4타이틀"), new Count(3)); + Pick pick5 = createPickCommentTotalCount(member, new Title("픽5타이틀"), new Count(4)); + Pick pick6 = createPickCommentTotalCount(member, new Title("픽6타이틀"), new Count(5)); pickRepository.saveAll(List.of(pick1, pick2, pick3, pick4, pick5, pick6)); @@ -170,18 +190,28 @@ void findPicksByCursorOrderByDesc() { ); } + private static Member createMember(String name) { + return Member.builder() + .name(name) + .isDeleted(false) + .build(); + } + @Test @DisplayName("인기순으로 Pick을 조회한다." + "(현재 가중치 = 댓글수:" + PickPopularScorePolicy.COMMENT_WEIGHT + ", 투표수:" + PickPopularScorePolicy.VOTE_WEIGHT + ", 조회수:" + PickPopularScorePolicy.VIEW_WEIGHT + ")") void findPicksByCursorOrderByPopular() { // given - Pick pick1 = createPickByPopularScore(new Title("픽1타이틀"), new Count(1)); - Pick pick2 = createPickByPopularScore(new Title("픽2타이틀"), new Count(2)); - Pick pick3 = createPickByPopularScore(new Title("픽3타이틀"), new Count(3)); - Pick pick4 = createPickByPopularScore(new Title("픽4타이틀"), new Count(3)); - Pick pick5 = createPickByPopularScore(new Title("픽5타이틀"), new Count(4)); - Pick pick6 = createPickByPopularScore(new Title("픽6타이틀"), new Count(5)); + Member member = createMember("사용자"); + memberRepository.save(member); + + Pick pick1 = createPickByPopularScore(member, new Title("픽1타이틀"), new Count(1)); + Pick pick2 = createPickByPopularScore(member, new Title("픽2타이틀"), new Count(2)); + Pick pick3 = createPickByPopularScore(member, new Title("픽3타이틀"), new Count(3)); + Pick pick4 = createPickByPopularScore(member, new Title("픽4타이틀"), new Count(3)); + Pick pick5 = createPickByPopularScore(member, new Title("픽5타이틀"), new Count(4)); + Pick pick6 = createPickByPopularScore(member, new Title("픽6타이틀"), new Count(5)); pickRepository.saveAll(List.of(pick1, pick2, pick3, pick4, pick5, pick6)); @@ -203,7 +233,10 @@ void findPicksByCursorOrderByPopular() { @DisplayName("findPickWithPickOptionWithPickVoteWithMemberByPickId 쿼리 확인") void findPickAndPickOptionAndPickOptionImageById() { // given - Pick pick = createPick(new Title("픽1타이틀")); + Member member = createMember("사용자"); + memberRepository.save(member); + + Pick pick = createPick(member, new Title("픽1타이틀")); pickRepository.save(pick); PickOption pickOption1 = createPickOption(new Title("픽옵션1"), new PickOptionContents("픽옵션콘텐츠1"), new Count(1), @@ -260,31 +293,35 @@ private PickOptionImage cratePickOptionImage(String name, PickOption pickOption) return pickOptionImage; } - private Pick createPick(Title title) { + private Pick createPick(Member member, Title title) { return Pick.builder() + .member(member) .title(title) .contentStatus(ContentStatus.APPROVAL) .build(); } - private Pick createPickByPopularScore(Title title, Count popularScore) { + private Pick createPickByPopularScore(Member member, Title title, Count popularScore) { return Pick.builder() + .member(member) .title(title) .popularScore(popularScore) .contentStatus(ContentStatus.APPROVAL) .build(); } - private Pick createPickViewTotalCount(Title title, Count viewTotalCount) { + private Pick createPickViewTotalCount(Member member, Title title, Count viewTotalCount) { return Pick.builder() + .member(member) .title(title) .viewTotalCount(viewTotalCount) .contentStatus(ContentStatus.APPROVAL) .build(); } - private Pick createPickCommentTotalCount(Title title, Count commentTotalCount) { + private Pick createPickCommentTotalCount(Member member, Title title, Count commentTotalCount) { return Pick.builder() + .member(member) .title(title) .commentTotalCount(commentTotalCount) .contentStatus(ContentStatus.APPROVAL) @@ -298,12 +335,13 @@ private Pick createPickVoteTotalCount(Title title, Count voteTotalCount) { .build(); } - private Pick createPick(Title title, Count pickVoteTotalCount, Count pickViewTotalCount, + private Pick createPick(Member member, Title title, Count pickVoteTotalCount, Count pickViewTotalCount, Count pickcommentTotalCount, String thumbnailUrl, String author, List pickOptions, List pickVotes ) { Pick pick = Pick.builder() + .member(member) .title(title) .voteTotalCount(pickVoteTotalCount) .viewTotalCount(pickViewTotalCount) diff --git a/src/test/java/com/dreamypatisiel/devdevdev/domain/repository/techArticle/TechArticleRepositoryTest.java b/src/test/java/com/dreamypatisiel/devdevdev/domain/repository/techArticle/TechArticleRepositoryTest.java index 9acc5d32..7980c0fc 100644 --- a/src/test/java/com/dreamypatisiel/devdevdev/domain/repository/techArticle/TechArticleRepositoryTest.java +++ b/src/test/java/com/dreamypatisiel/devdevdev/domain/repository/techArticle/TechArticleRepositoryTest.java @@ -10,7 +10,6 @@ import com.dreamypatisiel.devdevdev.domain.entity.embedded.Count; import com.dreamypatisiel.devdevdev.domain.entity.enums.Role; import com.dreamypatisiel.devdevdev.domain.entity.enums.SocialType; -import com.dreamypatisiel.devdevdev.domain.repository.BookmarkRepository; import com.dreamypatisiel.devdevdev.domain.repository.CompanyRepository; import com.dreamypatisiel.devdevdev.domain.repository.member.MemberRepository; import com.dreamypatisiel.devdevdev.global.security.oauth2.model.SocialMemberDto; diff --git a/src/test/java/com/dreamypatisiel/devdevdev/domain/service/blame/MemberPickBlameServiceTest.java b/src/test/java/com/dreamypatisiel/devdevdev/domain/service/blame/MemberPickBlameServiceTest.java index 7b377c05..20c4dee6 100644 --- a/src/test/java/com/dreamypatisiel/devdevdev/domain/service/blame/MemberPickBlameServiceTest.java +++ b/src/test/java/com/dreamypatisiel/devdevdev/domain/service/blame/MemberPickBlameServiceTest.java @@ -82,7 +82,7 @@ void blamePick() { memberRepository.save(member); // 픽픽픽 생성 - Pick pick = createPick("픽픽픽", ContentStatus.APPROVAL, new Count(0L)); + Pick pick = createPick(member, "픽픽픽", ContentStatus.APPROVAL, new Count(0L)); pickRepository.save(pick); // 신고 종류 생성 @@ -124,7 +124,7 @@ void blamePickEtc() { memberRepository.save(member); // 픽픽픽 생성 - Pick pick = createPick("픽픽픽", ContentStatus.APPROVAL, new Count(0L)); + Pick pick = createPick(member, "픽픽픽", ContentStatus.APPROVAL, new Count(0L)); pickRepository.save(pick); // 신고 종류 생성 @@ -185,7 +185,7 @@ void blamePickIsNotApproval(ContentStatus contentStatus) { memberRepository.save(member); // 픽픽픽 생성 - Pick pick = createPick("픽픽픽", contentStatus, new Count(0L)); + Pick pick = createPick(member, "픽픽픽", contentStatus, new Count(0L)); pickRepository.save(pick); BlamePickDto blamePickDto = new BlamePickDto(pick.getId(), null, 0L, null); @@ -206,7 +206,7 @@ void blamePickNotFoundBlameType() { memberRepository.save(member); // 픽픽픽 생성 - Pick pick = createPick("픽픽픽", ContentStatus.APPROVAL, new Count(0L)); + Pick pick = createPick(member, "픽픽픽", ContentStatus.APPROVAL, new Count(0L)); pickRepository.save(pick); BlamePickDto blamePickDto = new BlamePickDto(pick.getId(), null, 0L, null); @@ -227,7 +227,7 @@ void blamePickComment() { memberRepository.save(member); // 픽픽픽 생성 - Pick pick = createPick("픽픽픽", ContentStatus.APPROVAL, new Count(0L)); + Pick pick = createPick(member, "픽픽픽", ContentStatus.APPROVAL, new Count(0L)); pickRepository.save(pick); // 픽픽픽 댓글 생성 @@ -273,7 +273,7 @@ void blamePickCommentEtc() { memberRepository.save(member); // 픽픽픽 생성 - Pick pick = createPick("픽픽픽", ContentStatus.APPROVAL, new Count(0L)); + Pick pick = createPick(member, "픽픽픽", ContentStatus.APPROVAL, new Count(0L)); pickRepository.save(pick); // 픽픽픽 댓글 생성 @@ -338,7 +338,7 @@ void blamePickCommentNotFoundPickComment() { memberRepository.save(member); // 픽픽픽 생성 - Pick pick = createPick("픽픽픽", ContentStatus.APPROVAL, new Count(0L)); + Pick pick = createPick(member, "픽픽픽", ContentStatus.APPROVAL, new Count(0L)); pickRepository.save(pick); BlamePickDto blamePickDto = new BlamePickDto(pick.getId(), 0L, 0L, null); @@ -360,7 +360,7 @@ void blamePickCommentPickIsNotApproval(ContentStatus contentStatus) { memberRepository.save(member); // 픽픽픽 생성 - Pick pick = createPick("픽픽픽", contentStatus, new Count(0L)); + Pick pick = createPick(member, "픽픽픽", contentStatus, new Count(0L)); pickRepository.save(pick); // 픽픽픽 댓글 생성 @@ -385,7 +385,7 @@ void blamePickCommentIsDeleted() { memberRepository.save(member); // 픽픽픽 생성 - Pick pick = createPick("픽픽픽", ContentStatus.APPROVAL, new Count(0L)); + Pick pick = createPick(member, "픽픽픽", ContentStatus.APPROVAL, new Count(0L)); pickRepository.save(pick); // 삭제 상태의 픽픽픽 댓글 생성 @@ -411,7 +411,7 @@ void blamePickCommentNotFoundBlameType() { memberRepository.save(member); // 픽픽픽 생성 - Pick pick = createPick("픽픽픽", ContentStatus.APPROVAL, new Count(0L)); + Pick pick = createPick(member, "픽픽픽", ContentStatus.APPROVAL, new Count(0L)); pickRepository.save(pick); // 픽픽픽 댓글 생성 @@ -439,8 +439,9 @@ private PickComment createPickComment(Pick pick, Member createdBy, String conten return pickComment; } - private Pick createPick(String title, ContentStatus contentStatus, Count commentTotalCount) { + private Pick createPick(Member member, String title, ContentStatus contentStatus, Count commentTotalCount) { return Pick.builder() + .member(member) .title(new Title(title)) .contentStatus(contentStatus) .commentTotalCount(commentTotalCount) diff --git a/src/test/java/com/dreamypatisiel/devdevdev/domain/service/common/MemberBlameServiceTest.java b/src/test/java/com/dreamypatisiel/devdevdev/domain/service/common/MemberBlameServiceTest.java index 75eedcc4..200b75c0 100644 --- a/src/test/java/com/dreamypatisiel/devdevdev/domain/service/common/MemberBlameServiceTest.java +++ b/src/test/java/com/dreamypatisiel/devdevdev/domain/service/common/MemberBlameServiceTest.java @@ -148,7 +148,7 @@ void blameAlreadyExistBlamePick() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); // 픽픽픽 생성 - Pick pick = createPick("픽픽픽", ContentStatus.APPROVAL, new Count(0L)); + Pick pick = createPick(member, "픽픽픽", ContentStatus.APPROVAL, new Count(0L)); pickRepository.save(pick); // 신고 종류 생성 @@ -183,7 +183,7 @@ void blameAlreadyExistBlamePickComment() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); // 픽픽픽 생성 - Pick pick = createPick("픽픽픽", ContentStatus.APPROVAL, new Count(0L)); + Pick pick = createPick(member, "픽픽픽", ContentStatus.APPROVAL, new Count(0L)); pickRepository.save(pick); // 픽픽픽 댓글 생성 @@ -265,7 +265,7 @@ void blamePick() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); // 픽픽픽 생성 - Pick pick = createPick("픽픽픽", ContentStatus.APPROVAL, new Count(0L)); + Pick pick = createPick(member, "픽픽픽", ContentStatus.APPROVAL, new Count(0L)); pickRepository.save(pick); // 신고 종류 생성 @@ -313,7 +313,7 @@ void blamePickComment() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); // 픽픽픽 생성 - Pick pick = createPick("픽픽픽", ContentStatus.APPROVAL, new Count(0L)); + Pick pick = createPick(member, "픽픽픽", ContentStatus.APPROVAL, new Count(0L)); pickRepository.save(pick); // 픽픽픽 댓글 생성 @@ -513,8 +513,9 @@ private PickComment createPickComment(Pick pick, Member createdBy, String conten return pickComment; } - private Pick createPick(String title, ContentStatus contentStatus, Count commentTotalCount) { + private Pick createPick(Member member, String title, ContentStatus contentStatus, Count commentTotalCount) { return Pick.builder() + .member(member) .title(new Title(title)) .contentStatus(contentStatus) .commentTotalCount(commentTotalCount) diff --git a/src/test/java/com/dreamypatisiel/devdevdev/domain/service/member/MemberServiceTest.java b/src/test/java/com/dreamypatisiel/devdevdev/domain/service/member/MemberServiceTest.java index 1c5c9cc2..23c19471 100644 --- a/src/test/java/com/dreamypatisiel/devdevdev/domain/service/member/MemberServiceTest.java +++ b/src/test/java/com/dreamypatisiel/devdevdev/domain/service/member/MemberServiceTest.java @@ -5,10 +5,15 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.tuple; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import com.dreamypatisiel.devdevdev.domain.entity.Bookmark; +import com.dreamypatisiel.devdevdev.domain.entity.Company; import com.dreamypatisiel.devdevdev.domain.entity.Member; import com.dreamypatisiel.devdevdev.domain.entity.Pick; +import com.dreamypatisiel.devdevdev.domain.entity.PickComment; import com.dreamypatisiel.devdevdev.domain.entity.PickOption; import com.dreamypatisiel.devdevdev.domain.entity.PickVote; import com.dreamypatisiel.devdevdev.domain.entity.SurveyAnswer; @@ -17,6 +22,9 @@ import com.dreamypatisiel.devdevdev.domain.entity.SurveyVersion; import com.dreamypatisiel.devdevdev.domain.entity.SurveyVersionQuestionMapper; import com.dreamypatisiel.devdevdev.domain.entity.TechArticle; +import com.dreamypatisiel.devdevdev.domain.entity.TechComment; +import com.dreamypatisiel.devdevdev.domain.entity.embedded.CommentContents; +import com.dreamypatisiel.devdevdev.domain.entity.embedded.CompanyName; import com.dreamypatisiel.devdevdev.domain.entity.embedded.Count; import com.dreamypatisiel.devdevdev.domain.entity.embedded.CustomSurveyAnswer; import com.dreamypatisiel.devdevdev.domain.entity.embedded.PickOptionContents; @@ -25,8 +33,9 @@ import com.dreamypatisiel.devdevdev.domain.entity.enums.PickOptionType; import com.dreamypatisiel.devdevdev.domain.entity.enums.Role; import com.dreamypatisiel.devdevdev.domain.entity.enums.SocialType; -import com.dreamypatisiel.devdevdev.domain.repository.BookmarkRepository; +import com.dreamypatisiel.devdevdev.domain.repository.CompanyRepository; import com.dreamypatisiel.devdevdev.domain.repository.member.MemberRepository; +import com.dreamypatisiel.devdevdev.domain.repository.pick.PickCommentRepository; import com.dreamypatisiel.devdevdev.domain.repository.pick.PickOptionRepository; import com.dreamypatisiel.devdevdev.domain.repository.pick.PickRepository; import com.dreamypatisiel.devdevdev.domain.repository.pick.PickVoteRepository; @@ -35,26 +44,37 @@ import com.dreamypatisiel.devdevdev.domain.repository.survey.SurveyQuestionRepository; import com.dreamypatisiel.devdevdev.domain.repository.survey.SurveyVersionQuestionMapperRepository; import com.dreamypatisiel.devdevdev.domain.repository.survey.SurveyVersionRepository; +import com.dreamypatisiel.devdevdev.domain.repository.techArticle.BookmarkRepository; import com.dreamypatisiel.devdevdev.domain.repository.techArticle.TechArticleRepository; -import com.dreamypatisiel.devdevdev.web.dto.response.member.MemberExitSurveyQuestionOptionResponse; -import com.dreamypatisiel.devdevdev.web.dto.response.member.MemberExitSurveyQuestionResponse; -import com.dreamypatisiel.devdevdev.web.dto.response.member.MemberExitSurveyResponse; -import com.dreamypatisiel.devdevdev.web.dto.response.pick.MyPickMainResponse; -import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.TechArticleMainResponse; +import com.dreamypatisiel.devdevdev.domain.repository.techArticle.TechCommentRepository; import com.dreamypatisiel.devdevdev.elastic.domain.service.ElasticsearchSupportTest; import com.dreamypatisiel.devdevdev.exception.MemberException; import com.dreamypatisiel.devdevdev.exception.SurveyException; import com.dreamypatisiel.devdevdev.global.common.MemberProvider; import com.dreamypatisiel.devdevdev.global.security.oauth2.model.SocialMemberDto; import com.dreamypatisiel.devdevdev.global.security.oauth2.model.UserPrincipal; +import com.dreamypatisiel.devdevdev.web.dto.SliceCustom; +import com.dreamypatisiel.devdevdev.web.dto.request.comment.MyWrittenCommentFilter; +import com.dreamypatisiel.devdevdev.web.dto.request.comment.MyWrittenCommentRequest; import com.dreamypatisiel.devdevdev.web.dto.request.member.RecordMemberExitSurveyAnswerRequest; import com.dreamypatisiel.devdevdev.web.dto.request.member.RecordMemberExitSurveyQuestionOptionsRequest; +import com.dreamypatisiel.devdevdev.web.dto.response.comment.MyWrittenCommentResponse; +import com.dreamypatisiel.devdevdev.web.dto.response.member.MemberExitSurveyQuestionOptionResponse; +import com.dreamypatisiel.devdevdev.web.dto.response.member.MemberExitSurveyQuestionResponse; +import com.dreamypatisiel.devdevdev.web.dto.response.member.MemberExitSurveyResponse; +import com.dreamypatisiel.devdevdev.web.dto.response.pick.MyPickMainResponse; +import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.TechArticleMainResponse; import jakarta.persistence.EntityManager; +import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; +import org.assertj.core.groups.Tuple; 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.data.auditing.AuditingHandler; +import org.springframework.data.auditing.DateTimeProvider; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; @@ -102,6 +122,12 @@ class MemberServiceTest extends ElasticsearchSupportTest { SurveyQuestionOptionRepository surveyQuestionOptionRepository; @Autowired SurveyAnswerRepository surveyAnswerRepository; + @Autowired + CompanyRepository companyRepository; + @Autowired + TechCommentRepository techCommentRepository; + @Autowired + PickCommentRepository pickCommentRepository; @Test @DisplayName("회원이 회원탈퇴 설문조사를 완료하지 않으면 탈퇴가 불가능하다.") @@ -519,6 +545,579 @@ void recordMemberExitSurveyAnswerMemberException() { .hasMessage(INVALID_MEMBER_NOT_FOUND_MESSAGE); } + @Test + @DisplayName("회원이 작성한 픽픽픽, 기술블로그 댓글을 작성시간 내림차순으로 무한스크롤 방식으로 조회한다.") + void findMyWrittenComments_ALL() { + // given + DateTimeProvider dateTimeProvider = mock(DateTimeProvider.class); + AuditingHandler auditingHandler = mock(AuditingHandler.class); + auditingHandler.setDateTimeProvider(dateTimeProvider); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 1, 1, 0, 0))); + + // 회원 생성 + SocialMemberDto socialMemberDto = createSocialDto(userId, name, nickname, password, email, socialType, role); + Member member = Member.createMemberBy(socialMemberDto); + memberRepository.save(member); + + UserPrincipal userPrincipal = UserPrincipal.createByMember(member); + SecurityContext context = SecurityContextHolder.getContext(); + context.setAuthentication(new OAuth2AuthenticationToken(userPrincipal, userPrincipal.getAuthorities(), + userPrincipal.getSocialType().name())); + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + // 픽픽픽 생성 + Pick pick = createPick("픽픽픽", member); + pickRepository.save(pick); + + // 픽픽픽 옵션 생성 + PickOption pickOption = createPickOption(pick, "픽픽픽 A", PickOptionType.firstPickOption); + pickOptionRepository.save(pickOption); + + // 픽픽픽 투표 생성 + PickVote pickVote = createPickVote(pick, pickOption, member); + pickVoteRepository.save(pickVote); + + // 픽픽픽 댓글 생성 + PickComment pickComment1 = createPickComment(pick, member, pickVote, null, null, "픽픽픽 댓글1", true, 0L); + PickComment pickComment2 = createPickComment(pick, member, null, pickComment1, pickComment1, "픽픽픽 댓글2", false, + 1L); + PickComment pickComment3 = createPickComment(pick, member, null, pickComment1, pickComment2, "픽픽픽 댓글3", false, + 2L); + PickComment pickComment4 = createPickComment(pick, member, null, pickComment1, pickComment3, "픽픽픽 댓글4", false, + 3L); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 1, 1, 0, 0, 0, 0))); + pickCommentRepository.save(pickComment1); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 2, 1, 0, 0, 0, 0))); + pickCommentRepository.save(pickComment2); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 3, 1, 0, 0, 0, 0))); + pickCommentRepository.save(pickComment3); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 4, 1, 0, 0, 0, 0))); + pickCommentRepository.save(pickComment4); + + // 기술블로그 회사 생성 + Company company = createCompany("DreamyPatisiel"); + companyRepository.save(company); + + // 기술블로그 생성 + TechArticle techArticle = createTechArticle(company, "기술블로그 제목"); + techArticleRepository.save(techArticle); + + // 기술블로그 댓글 생성 + TechComment techComment1 = createTechComment(techArticle, member, null, null, "기술블로그 댓글1", 0L); + TechComment techComment2 = createTechComment(techArticle, member, techComment1, techComment1, "기술블로그 댓글2", 1L); + TechComment techComment3 = createTechComment(techArticle, member, techComment1, techComment2, "기술블로그 댓글3", 2L); + TechComment techComment4 = createTechComment(techArticle, member, techComment1, techComment3, "기술블로그 댓글4", 3L); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 5, 1, 0, 0, 0, 0))); + techCommentRepository.save(techComment1); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 6, 1, 0, 0, 0, 0))); + techCommentRepository.save(techComment2); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 7, 1, 0, 0, 0, 0))); + techCommentRepository.save(techComment3); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 8, 1, 0, 0, 0, 0))); + techCommentRepository.save(techComment4); + + Pageable pageable = PageRequest.of(0, 6); + + // 첫 번째 페이지 + // when1 + Long techCommentId = techComment4.getId() + 1L; + Long pickCommentId = pickComment4.getId() + 1L; + + MyWrittenCommentRequest myWrittenCommentRequest = new MyWrittenCommentRequest(pickCommentId, techCommentId, + MyWrittenCommentFilter.ALL); + + SliceCustom page1 = memberService.findMyWrittenComments(pageable, + myWrittenCommentRequest, authentication); + + // then1 + assertAll( + () -> assertThat(page1.getTotalElements()).isEqualTo(8), + () -> assertThat(page1.hasNext()).isEqualTo(true) + ); + + assertThat(page1.getContent()).hasSize(6) + .extracting("postId", "postTitle", "commentId", "commentType", "commentContents", + "commentRecommendTotalCount", "pickOptionTitle", "pickOptionType") + .containsExactly( + Tuple.tuple(techArticle.getId(), techArticle.getTitle().getTitle(), techComment4.getId(), + "TECH_ARTICLE", techComment4.getContents().getCommentContents(), + techComment4.getRecommendTotalCount().getCount(), + null, null), + Tuple.tuple(techArticle.getId(), techArticle.getTitle().getTitle(), techComment3.getId(), + "TECH_ARTICLE", techComment3.getContents().getCommentContents(), + techComment3.getRecommendTotalCount().getCount(), + null, null), + Tuple.tuple(techArticle.getId(), techArticle.getTitle().getTitle(), techComment2.getId(), + "TECH_ARTICLE", techComment2.getContents().getCommentContents(), + techComment2.getRecommendTotalCount().getCount(), + null, null), + Tuple.tuple(techArticle.getId(), techArticle.getTitle().getTitle(), techComment1.getId(), + "TECH_ARTICLE", techComment1.getContents().getCommentContents(), + techComment1.getRecommendTotalCount().getCount(), + null, null), + Tuple.tuple(pick.getId(), pick.getTitle().getTitle(), pickComment4.getId(), + "PICK", pickComment4.getContents().getCommentContents(), + pickComment4.getRecommendTotalCount().getCount(), + null, null), + Tuple.tuple(pick.getId(), pick.getTitle().getTitle(), pickComment3.getId(), + "PICK", pickComment3.getContents().getCommentContents(), + pickComment3.getRecommendTotalCount().getCount(), + null, null) + ); + + // 두 번째 페이지 + // when2 + techCommentId = techComment1.getId(); + pickCommentId = pickComment3.getId(); + + MyWrittenCommentRequest myWrittenCommentRequest2 = new MyWrittenCommentRequest(pickCommentId, techCommentId, + MyWrittenCommentFilter.ALL); + + SliceCustom page2 = memberService.findMyWrittenComments(pageable, + myWrittenCommentRequest2, authentication); + + // then2 + assertAll( + () -> assertThat(page2.getTotalElements()).isEqualTo(8), + () -> assertThat(page2.hasNext()).isEqualTo(false) + ); + + assertThat(page2.getContent()).hasSize(2) + .extracting("postId", "postTitle", "commentId", "commentType", "commentContents", + "commentRecommendTotalCount", "pickOptionTitle", "pickOptionType") + .containsExactly( + Tuple.tuple(pick.getId(), pick.getTitle().getTitle(), pickComment2.getId(), + "PICK", pickComment2.getContents().getCommentContents(), + pickComment2.getRecommendTotalCount().getCount(), + null, null), + Tuple.tuple(pick.getId(), pick.getTitle().getTitle(), pickComment1.getId(), + "PICK", pickComment1.getContents().getCommentContents(), + pickComment1.getRecommendTotalCount().getCount(), + pickOption.getTitle().getTitle(), pickOption.getPickOptionType().name()) + ); + } + + @Test + @DisplayName("픽픽픽, 기술블로그 댓글을 작성시간 내림차순으로 무한스크롤 방식으로 조회할 때 회원이 아니면 예외가 발생한다.") + void findMyWrittenComments_INVALID_MEMBER_NOT_FOUND_MESSAGE() { + // given + UserPrincipal userPrincipal = UserPrincipal.createByEmailAndRoleAndSocialType(email, role, socialType); + SecurityContext context = SecurityContextHolder.getContext(); + context.setAuthentication(new OAuth2AuthenticationToken(userPrincipal, userPrincipal.getAuthorities(), + userPrincipal.getSocialType().name())); + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + Pageable pageable = PageRequest.of(0, 6); + MyWrittenCommentRequest myWrittenCommentRequest = new MyWrittenCommentRequest(0L, 0L, + MyWrittenCommentFilter.ALL); + + // when // then + assertThatThrownBy(() -> memberService.findMyWrittenComments(pageable, myWrittenCommentRequest, authentication)) + .isInstanceOf(MemberException.class) + .hasMessage(INVALID_MEMBER_NOT_FOUND_MESSAGE); + } + + @Test + @DisplayName("회원이 작성한 픽픽픽 댓글을 작성시간 내림차순으로 무한스크롤 방식으로 조회한다.") + void findMyWrittenComments_PICK() { + // given + DateTimeProvider dateTimeProvider = mock(DateTimeProvider.class); + AuditingHandler auditingHandler = mock(AuditingHandler.class); + auditingHandler.setDateTimeProvider(dateTimeProvider); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 1, 1, 0, 0, 0, 0))); + + // 회원 생성 + SocialMemberDto socialMemberDto = createSocialDto(userId, name, nickname, password, email, socialType, role); + Member member = Member.createMemberBy(socialMemberDto); + memberRepository.save(member); + + UserPrincipal userPrincipal = UserPrincipal.createByMember(member); + SecurityContext context = SecurityContextHolder.getContext(); + context.setAuthentication(new OAuth2AuthenticationToken(userPrincipal, userPrincipal.getAuthorities(), + userPrincipal.getSocialType().name())); + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + // 픽픽픽 생성 + Pick pick = createPick("픽픽픽", member); + pickRepository.save(pick); + + // 픽픽픽 옵션 생성 + PickOption pickOption = createPickOption(pick, "픽픽픽 A", PickOptionType.firstPickOption); + pickOptionRepository.save(pickOption); + + // 픽픽픽 투표 생성 + PickVote pickVote = createPickVote(pick, pickOption, member); + pickVoteRepository.save(pickVote); + + // 픽픽픽 댓글 생성 + PickComment pickComment1 = createPickComment(pick, member, pickVote, null, null, "픽픽픽 댓글1", true, 0L); + PickComment pickComment2 = createPickComment(pick, member, null, pickComment1, pickComment1, "픽픽픽 댓글2", false, + 1L); + PickComment pickComment3 = createPickComment(pick, member, null, pickComment1, pickComment2, "픽픽픽 댓글3", false, + 2L); + PickComment pickComment4 = createPickComment(pick, member, null, pickComment1, pickComment3, "픽픽픽 댓글4", false, + 3L); + PickComment pickComment5 = createPickComment(pick, member, null, pickComment1, pickComment4, "픽픽픽 댓글5", false, + 4L); + PickComment pickComment6 = createPickComment(pick, member, null, pickComment1, pickComment5, "픽픽픽 댓글6", false, + 5L); + PickComment pickComment7 = createPickComment(pick, member, null, pickComment1, pickComment6, "픽픽픽 댓글7", false, + 6L); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 1, 1, 0, 0, 0, 0))); + pickCommentRepository.save(pickComment1); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 2, 1, 0, 0, 0, 0))); + pickCommentRepository.save(pickComment2); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 3, 1, 0, 0, 0, 0))); + pickCommentRepository.save(pickComment3); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 4, 1, 0, 0, 0, 0))); + pickCommentRepository.save(pickComment4); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 5, 1, 0, 0, 0, 0))); + pickCommentRepository.save(pickComment5); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 6, 1, 0, 0, 0, 0))); + pickCommentRepository.save(pickComment6); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 7, 1, 0, 0, 0, 0))); + pickCommentRepository.save(pickComment7); + + // 기술블로그 회사 생성 + Company company = createCompany("DreamyPatisiel"); + companyRepository.save(company); + + // 기술블로그 생성 + TechArticle techArticle = createTechArticle(company, "기술블로그 제목"); + techArticleRepository.save(techArticle); + + // 기술블로그 댓글 생성 + TechComment techComment1 = createTechComment(techArticle, member, null, null, "기술블로그 댓글1", 0L); + TechComment techComment2 = createTechComment(techArticle, member, techComment1, techComment1, "기술블로그 댓글2", 1L); + TechComment techComment3 = createTechComment(techArticle, member, techComment1, techComment2, "기술블로그 댓글3", 2L); + TechComment techComment4 = createTechComment(techArticle, member, techComment1, techComment3, "기술블로그 댓글4", 3L); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 5, 1, 0, 0, 0, 0))); + techCommentRepository.save(techComment1); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 6, 1, 0, 0, 0, 0))); + techCommentRepository.save(techComment2); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 7, 1, 0, 0, 0, 0))); + techCommentRepository.save(techComment3); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 8, 1, 0, 0, 0, 0))); + techCommentRepository.save(techComment4); + + Pageable pageable = PageRequest.of(0, 6); + + // 첫 번째 페이지 + Long techCommentId = techComment4.getId() + 1L; + Long pickCommentId = pickComment7.getId() + 1L; + + MyWrittenCommentRequest myWrittenCommentRequest1 = new MyWrittenCommentRequest(pickCommentId, techCommentId, + MyWrittenCommentFilter.PICK); + + SliceCustom page1 = memberService.findMyWrittenComments(pageable, + myWrittenCommentRequest1, authentication); + + // then + assertAll( + () -> assertThat(page1.getTotalElements()).isEqualTo(7), + () -> assertThat(page1.hasNext()).isEqualTo(true) + ); + + assertThat(page1.getContent()).hasSize(6) + .extracting("postId", "postTitle", "commentId", "commentType", "commentContents", + "commentRecommendTotalCount", "pickOptionTitle", "pickOptionType") + .containsExactly( + Tuple.tuple(pick.getId(), pick.getTitle().getTitle(), pickComment7.getId(), + "PICK", pickComment7.getContents().getCommentContents(), + pickComment7.getRecommendTotalCount().getCount(), + null, null), + Tuple.tuple(pick.getId(), pick.getTitle().getTitle(), pickComment6.getId(), + "PICK", pickComment6.getContents().getCommentContents(), + pickComment6.getRecommendTotalCount().getCount(), + null, null), + Tuple.tuple(pick.getId(), pick.getTitle().getTitle(), pickComment5.getId(), + "PICK", pickComment5.getContents().getCommentContents(), + pickComment5.getRecommendTotalCount().getCount(), + null, null), + Tuple.tuple(pick.getId(), pick.getTitle().getTitle(), pickComment4.getId(), + "PICK", pickComment4.getContents().getCommentContents(), + pickComment4.getRecommendTotalCount().getCount(), + null, null), + Tuple.tuple(pick.getId(), pick.getTitle().getTitle(), pickComment3.getId(), + "PICK", pickComment3.getContents().getCommentContents(), + pickComment3.getRecommendTotalCount().getCount(), + null, null), + Tuple.tuple(pick.getId(), pick.getTitle().getTitle(), pickComment2.getId(), + "PICK", pickComment2.getContents().getCommentContents(), + pickComment2.getRecommendTotalCount().getCount(), + null, null) + ); + + pickCommentId = pickComment2.getId(); + + MyWrittenCommentRequest myWrittenCommentRequest2 = new MyWrittenCommentRequest(pickCommentId, techCommentId, + MyWrittenCommentFilter.PICK); + + SliceCustom page2 = memberService.findMyWrittenComments(pageable, + myWrittenCommentRequest2, authentication); + + // then + assertAll( + () -> assertThat(page2.getTotalElements()).isEqualTo(7), + () -> assertThat(page2.hasNext()).isEqualTo(false) + ); + + assertThat(page2.getContent()).hasSize(1) + .extracting("postId", "postTitle", "commentId", "commentType", "commentContents", + "commentRecommendTotalCount", "pickOptionTitle", "pickOptionType") + .containsExactly( + Tuple.tuple(pick.getId(), pick.getTitle().getTitle(), pickComment1.getId(), + "PICK", pickComment1.getContents().getCommentContents(), + pickComment1.getRecommendTotalCount().getCount(), + pickOption.getTitle().getTitle(), pickOption.getPickOptionType().name()) + ); + } + + @Test + @DisplayName("회원이 작성한 기술블로그 댓글을 작성시간 내림차순으로 무한스크롤 방식으로 조회한다.") + void findMyWrittenComments_TECH() { + // given + DateTimeProvider dateTimeProvider = mock(DateTimeProvider.class); + AuditingHandler auditingHandler = mock(AuditingHandler.class); + auditingHandler.setDateTimeProvider(dateTimeProvider); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 1, 1, 0, 0, 0, 0))); + + // 회원 생성 + SocialMemberDto socialMemberDto = createSocialDto(userId, name, nickname, password, email, socialType, role); + Member member = Member.createMemberBy(socialMemberDto); + memberRepository.save(member); + + UserPrincipal userPrincipal = UserPrincipal.createByMember(member); + SecurityContext context = SecurityContextHolder.getContext(); + context.setAuthentication(new OAuth2AuthenticationToken(userPrincipal, userPrincipal.getAuthorities(), + userPrincipal.getSocialType().name())); + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + // 픽픽픽 생성 + Pick pick = createPick("픽픽픽", member); + pickRepository.save(pick); + + // 픽픽픽 옵션 생성 + PickOption pickOption = createPickOption(pick, "픽픽픽 A", PickOptionType.firstPickOption); + pickOptionRepository.save(pickOption); + + // 픽픽픽 투표 생성 + PickVote pickVote = createPickVote(pick, pickOption, member); + pickVoteRepository.save(pickVote); + + // 픽픽픽 댓글 생성 + PickComment pickComment1 = createPickComment(pick, member, pickVote, null, null, "픽픽픽 댓글1", true, 0L); + PickComment pickComment2 = createPickComment(pick, member, null, pickComment1, pickComment1, "픽픽픽 댓글2", false, + 1L); + PickComment pickComment3 = createPickComment(pick, member, null, pickComment1, pickComment2, "픽픽픽 댓글3", false, + 2L); + PickComment pickComment4 = createPickComment(pick, member, null, pickComment1, pickComment3, "픽픽픽 댓글4", false, + 3L); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 1, 1, 0, 0, 0, 0))); + pickCommentRepository.save(pickComment1); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 2, 1, 0, 0, 0, 0))); + pickCommentRepository.save(pickComment2); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 3, 1, 0, 0, 0, 0))); + pickCommentRepository.save(pickComment3); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 4, 1, 0, 0, 0, 0))); + pickCommentRepository.save(pickComment4); + + // 기술블로그 회사 생성 + Company company = createCompany("DreamyPatisiel"); + companyRepository.save(company); + + // 기술블로그 생성 + TechArticle techArticle = createTechArticle(company, "기술블로그 제목"); + techArticleRepository.save(techArticle); + + // 기술블로그 댓글 생성 + TechComment techComment1 = createTechComment(techArticle, member, null, null, "기술블로그 댓글1", 0L); + TechComment techComment2 = createTechComment(techArticle, member, techComment1, techComment1, "기술블로그 댓글2", 1L); + TechComment techComment3 = createTechComment(techArticle, member, techComment1, techComment2, "기술블로그 댓글3", 2L); + TechComment techComment4 = createTechComment(techArticle, member, techComment1, techComment3, "기술블로그 댓글4", 3L); + TechComment techComment5 = createTechComment(techArticle, member, techComment1, techComment4, "기술블로그 댓글5", 4L); + TechComment techComment6 = createTechComment(techArticle, member, techComment1, techComment5, "기술블로그 댓글6", 5L); + TechComment techComment7 = createTechComment(techArticle, member, techComment1, techComment6, "기술블로그 댓글7", 6L); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 5, 1, 0, 0, 0, 0))); + techCommentRepository.save(techComment1); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 6, 1, 0, 0, 0, 0))); + techCommentRepository.save(techComment2); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 7, 1, 0, 0, 0, 0))); + techCommentRepository.save(techComment3); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 8, 1, 0, 0, 0, 0))); + techCommentRepository.save(techComment4); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 9, 1, 0, 0, 0, 0))); + techCommentRepository.save(techComment5); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 10, 1, 0, 0, 0, 0))); + techCommentRepository.save(techComment6); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.of(2024, 11, 1, 0, 0, 0, 0))); + techCommentRepository.save(techComment7); + + Pageable pageable = PageRequest.of(0, 6); + + // 첫 번째 페이지 + // when1 + Long techCommentId = techComment7.getId() + 1L; + Long pickCommentId = pickComment4.getId() + 1L; + + MyWrittenCommentRequest myWrittenCommentRequest1 = new MyWrittenCommentRequest(pickCommentId, techCommentId, + MyWrittenCommentFilter.TECH_ARTICLE); + + SliceCustom page1 = memberService.findMyWrittenComments(pageable, + myWrittenCommentRequest1, authentication); + + // then1 + assertAll( + () -> assertThat(page1.getTotalElements()).isEqualTo(7), + () -> assertThat(page1.hasNext()).isEqualTo(true) + ); + + assertThat(page1.getContent()).hasSize(6) + .extracting("postId", "postTitle", "commentId", "commentType", "commentContents", + "commentRecommendTotalCount", "pickOptionTitle", "pickOptionType") + .containsExactly( + Tuple.tuple(techArticle.getId(), techArticle.getTitle().getTitle(), techComment7.getId(), + "TECH_ARTICLE", techComment7.getContents().getCommentContents(), + techComment7.getRecommendTotalCount().getCount(), + null, null), + Tuple.tuple(techArticle.getId(), techArticle.getTitle().getTitle(), techComment6.getId(), + "TECH_ARTICLE", techComment6.getContents().getCommentContents(), + techComment6.getRecommendTotalCount().getCount(), + null, null), + Tuple.tuple(techArticle.getId(), techArticle.getTitle().getTitle(), techComment5.getId(), + "TECH_ARTICLE", techComment5.getContents().getCommentContents(), + techComment5.getRecommendTotalCount().getCount(), + null, null), + Tuple.tuple(techArticle.getId(), techArticle.getTitle().getTitle(), techComment4.getId(), + "TECH_ARTICLE", techComment4.getContents().getCommentContents(), + techComment4.getRecommendTotalCount().getCount(), + null, null), + Tuple.tuple(techArticle.getId(), techArticle.getTitle().getTitle(), techComment3.getId(), + "TECH_ARTICLE", techComment3.getContents().getCommentContents(), + techComment3.getRecommendTotalCount().getCount(), + null, null), + Tuple.tuple(techArticle.getId(), techArticle.getTitle().getTitle(), techComment2.getId(), + "TECH_ARTICLE", techComment2.getContents().getCommentContents(), + techComment2.getRecommendTotalCount().getCount(), + null, null) + ); + + // 두 번째 페이지 + // when2 + techCommentId = techComment2.getId(); + + MyWrittenCommentRequest myWrittenCommentRequest2 = new MyWrittenCommentRequest(pickCommentId, techCommentId, + MyWrittenCommentFilter.TECH_ARTICLE); + + SliceCustom page2 = memberService.findMyWrittenComments(pageable, + myWrittenCommentRequest2, authentication); + + // then2 + assertAll( + () -> assertThat(page2.getTotalElements()).isEqualTo(7), + () -> assertThat(page2.hasNext()).isEqualTo(false) + ); + + assertThat(page2.getContent()).hasSize(1) + .extracting("postId", "postTitle", "commentId", "commentType", "commentContents", + "commentRecommendTotalCount", "pickOptionTitle", "pickOptionType") + .containsExactly( + Tuple.tuple(techArticle.getId(), techArticle.getTitle().getTitle(), techComment1.getId(), + "TECH_ARTICLE", techComment1.getContents().getCommentContents(), + techComment1.getRecommendTotalCount().getCount(), + null, null) + ); + } + + private static TechComment createTechComment(TechArticle techArticle, Member member, TechComment originParent, + TechComment parent, String contents, Long recommendTotalCount) { + return TechComment.builder() + .techArticle(techArticle) + .createdBy(member) + .originParent(originParent) + .parent(parent) + .contents(new CommentContents(contents)) + .recommendTotalCount(new Count(recommendTotalCount)) + .build(); + } + + private static TechArticle createTechArticle(Company company, String title) { + return TechArticle.builder() + .company(company) + .title(new Title(title)) + .build(); + } + + private static Company createCompany(String companyName) { + return Company.builder() + .name(new CompanyName(companyName)) + .build(); + } + + private static PickOption createPickOption(Pick pick, String title, PickOptionType pickOptionType) { + return PickOption.builder() + .pick(pick) + .title(new Title(title)) + .pickOptionType(pickOptionType) + .build(); + } + + private static Pick createPick(String title, Member member) { + return Pick.builder() + .title(new Title(title)) + .member(member) + .contentStatus(ContentStatus.APPROVAL) + .build(); + } + + private static PickComment createPickComment(Pick pick, Member member, PickVote pickVote, PickComment originParent, + PickComment parent, String contents, Boolean isPublic, + Long recommendTotalCount) { + return PickComment.builder() + .pick(pick) + .createdBy(member) + .pickVote(pickVote) + .originParent(originParent) + .parent(parent) + .contents(new CommentContents(contents)) + .isPublic(isPublic) + .recommendTotalCount(new Count(recommendTotalCount)) + .build(); + } + private Long pickSetup(Member member, ContentStatus contentStatus, Title pickTitle, Title firstPickOptionTitle, PickOptionContents firstPickOptionContents, Title secondPickOptinTitle, PickOptionContents secondPickOptionContents) { diff --git a/src/test/java/com/dreamypatisiel/devdevdev/domain/service/pick/GuestPickServiceTest.java b/src/test/java/com/dreamypatisiel/devdevdev/domain/service/pick/GuestPickServiceTest.java index 4581a337..eca4d2c4 100644 --- a/src/test/java/com/dreamypatisiel/devdevdev/domain/service/pick/GuestPickServiceTest.java +++ b/src/test/java/com/dreamypatisiel/devdevdev/domain/service/pick/GuestPickServiceTest.java @@ -112,13 +112,16 @@ class GuestPickServiceTest { @DisplayName("익명 회원이 커서 방식으로 익명 회원 전용 픽픽픽 메인을 조회한다.") void findPicksMain() { // given + SocialMemberDto socialMemberDto = createSocialDto(userId, name, nickname, password, email, socialType, role); + Member member = Member.createMemberBy(socialMemberDto); + memberRepository.save(member); + Count pickVoteTotalCount = new Count(pickOptionVoteCount1.getCount() + pickOptionVoteCount2.getCount()); Count pickViewTotalCount = new Count(1); Count pickCommentTotalCount = new Count(0); Count pickPopularScore = new Count(0); - Pick pick = createPick(pickTitle, pickVoteTotalCount, pickViewTotalCount, pickCommentTotalCount, - pickPopularScore, - thumbnailUrl, author, List.of()); + Pick pick = createPick(member, pickTitle, pickVoteTotalCount, pickViewTotalCount, pickCommentTotalCount, + pickPopularScore, thumbnailUrl, author, List.of()); pickRepository.save(pick); PickOption pickOption1 = createPickOption(pick, pickOptionTitle1, pickOptionContents1, pickOptionVoteCount1, @@ -161,6 +164,10 @@ void findPicksMain() { @DisplayName("익명 회원이 커서 방식으로 익명 회원 전용 조회수 내림차순으로 픽픽픽 메인을 조회한다.") void findPicksMainMOST_VIEWED() { // given + SocialMemberDto socialMemberDto = createSocialDto(userId, name, nickname, password, email, socialType, role); + Member member = Member.createMemberBy(socialMemberDto); + memberRepository.save(member); + Title title1 = new Title("픽1타이틀"); Title title2 = new Title("픽2타이틀"); Title title3 = new Title("픽3타이틀"); @@ -170,9 +177,12 @@ void findPicksMainMOST_VIEWED() { Count pick3ViewTotalCount = new Count(3); Count count = new Count(0); - Pick pick1 = createPick(title1, count, pick1ViewTotalCount, count, count, thumbnailUrl, author, List.of()); - Pick pick2 = createPick(title2, count, pick2ViewTotalCount, count, count, thumbnailUrl, author, List.of()); - Pick pick3 = createPick(title3, count, pick3ViewTotalCount, count, count, thumbnailUrl, author, List.of()); + Pick pick1 = createPick(member, title1, count, pick1ViewTotalCount, count, count, thumbnailUrl, author, + List.of()); + Pick pick2 = createPick(member, title2, count, pick2ViewTotalCount, count, count, thumbnailUrl, author, + List.of()); + Pick pick3 = createPick(member, title3, count, pick3ViewTotalCount, count, count, thumbnailUrl, author, + List.of()); pickRepository.saveAll(List.of(pick1, pick2, pick3)); PickOption pickOption1 = createPickOption(pick1, new Title("픽옵션1"), new PickOptionContents("픽콘텐츠1"), @@ -215,14 +225,18 @@ void findPicksMainMOST_VIEWED() { @DisplayName("익명 회원이 커서 방식으로 익명 회원 전용 생성시간 내림차순으로 픽픽픽 메인을 조회한다.") void findPicksMainLATEST() { // given + SocialMemberDto socialMemberDto = createSocialDto(userId, name, nickname, password, email, socialType, role); + Member member = Member.createMemberBy(socialMemberDto); + memberRepository.save(member); + Title title1 = new Title("픽1타이틀"); Title title2 = new Title("픽2타이틀"); Title title3 = new Title("픽3타이틀"); Count count = new Count(0); - Pick pick1 = createPick(title1, count, count, count, count, thumbnailUrl, author, List.of()); - Pick pick2 = createPick(title2, count, count, count, count, thumbnailUrl, author, List.of()); - Pick pick3 = createPick(title3, count, count, count, count, thumbnailUrl, author, List.of()); + Pick pick1 = createPick(member, title1, count, count, count, count, thumbnailUrl, author, List.of()); + Pick pick2 = createPick(member, title2, count, count, count, count, thumbnailUrl, author, List.of()); + Pick pick3 = createPick(member, title3, count, count, count, count, thumbnailUrl, author, List.of()); pickRepository.saveAll(List.of(pick1, pick2, pick3)); PickOption pickOption1 = createPickOption(pick1, new Title("픽옵션1"), new PickOptionContents("픽콘텐츠1"), @@ -265,6 +279,9 @@ void findPicksMainLATEST() { @DisplayName("익명 회원가 커서 방식으로 익명 회원 전용 댓글수 내림차순으로 픽픽픽 메인을 조회한다.") void findPicksMainMOST_COMMENTED() { // given + SocialMemberDto socialMemberDto = createSocialDto(userId, name, nickname, password, email, socialType, role); + Member member = Member.createMemberBy(socialMemberDto); + memberRepository.save(member); Title title1 = new Title("픽1타이틀"); Title title2 = new Title("픽2타이틀"); @@ -274,9 +291,12 @@ void findPicksMainMOST_COMMENTED() { Count pick3commentTotalCount = new Count(3); Count count = new Count(0); - Pick pick1 = createPick(title1, count, count, pick1commentTotalCount, count, thumbnailUrl, author, List.of()); - Pick pick2 = createPick(title2, count, count, pick2commentTotalCount, count, thumbnailUrl, author, List.of()); - Pick pick3 = createPick(title3, count, count, pick3commentTotalCount, count, thumbnailUrl, author, List.of()); + Pick pick1 = createPick(member, title1, count, count, pick1commentTotalCount, count, thumbnailUrl, author, + List.of()); + Pick pick2 = createPick(member, title2, count, count, pick2commentTotalCount, count, thumbnailUrl, author, + List.of()); + Pick pick3 = createPick(member, title3, count, count, pick3commentTotalCount, count, thumbnailUrl, author, + List.of()); pickRepository.saveAll(List.of(pick1, pick2, pick3)); PickOption pickOption1 = createPickOption(pick1, new Title("픽옵션1"), new PickOptionContents("픽콘텐츠1"), @@ -321,6 +341,10 @@ void findPicksMainMOST_COMMENTED() { + ", 조회수:" + PickPopularScorePolicy.VIEW_WEIGHT + ")") void findPicksMainPOPULAR() { // given + SocialMemberDto socialMemberDto = createSocialDto(userId, name, nickname, password, email, socialType, role); + Member member = Member.createMemberBy(socialMemberDto); + memberRepository.save(member); + Title title1 = new Title("픽1타이틀"); Title title2 = new Title("픽2타이틀"); Title title3 = new Title("픽3타이틀"); @@ -337,15 +361,15 @@ void findPicksMainPOPULAR() { Count pick3VoteTotalCount = new Count(1); Count pick3ViewTotalCount = new Count(2); - Pick pick1 = createPick(title1, pick1VoteTotalCount, pick1ViewTotalCount, pick1commentTotalCount, + Pick pick1 = createPick(member, title1, pick1VoteTotalCount, pick1ViewTotalCount, pick1commentTotalCount, thumbnailUrl, author, List.of()); pick1.changePopularScore(pickPopularScorePolicy); - Pick pick2 = createPick(title2, pick2VoteTotalCount, pick2ViewTotalCount, pick2commentTotalCount, + Pick pick2 = createPick(member, title2, pick2VoteTotalCount, pick2ViewTotalCount, pick2commentTotalCount, thumbnailUrl, author, List.of()); pick2.changePopularScore(pickPopularScorePolicy); - Pick pick3 = createPick(title3, pick3VoteTotalCount, pick3ViewTotalCount, pick3commentTotalCount, + Pick pick3 = createPick(member, title3, pick3VoteTotalCount, pick3ViewTotalCount, pick3commentTotalCount, thumbnailUrl, author, List.of()); pick3.changePopularScore(pickPopularScorePolicy); @@ -929,12 +953,13 @@ private PickOptionImage createPickOptionImage(String name, String imageUrl, Pick return pickOptionImage; } - private Pick createPick(Title title, Count pickVoteTotalCount, Count pickViewTotalCount, + private Pick createPick(Member member, Title title, Count pickVoteTotalCount, Count pickViewTotalCount, Count pickcommentTotalCount, Count pickPopularScore, String thumbnailUrl, String author, List pickVotes ) { Pick pick = Pick.builder() + .member(member) .title(title) .voteTotalCount(pickVoteTotalCount) .viewTotalCount(pickViewTotalCount) @@ -950,12 +975,13 @@ private Pick createPick(Title title, Count pickVoteTotalCount, Count pickViewTot return pick; } - private Pick createPick(Title title, Count pickVoteTotalCount, Count pickViewTotalCount, + private Pick createPick(Member member, Title title, Count pickVoteTotalCount, Count pickViewTotalCount, Count pickcommentTotalCount, String thumbnailUrl, String author, List pickVotes ) { Pick pick = Pick.builder() + .member(member) .title(title) .voteTotalCount(pickVoteTotalCount) .viewTotalCount(pickViewTotalCount) diff --git a/src/test/java/com/dreamypatisiel/devdevdev/domain/service/pick/MemberPickServiceTest.java b/src/test/java/com/dreamypatisiel/devdevdev/domain/service/pick/MemberPickServiceTest.java index 2ec6a17a..9493a32a 100644 --- a/src/test/java/com/dreamypatisiel/devdevdev/domain/service/pick/MemberPickServiceTest.java +++ b/src/test/java/com/dreamypatisiel/devdevdev/domain/service/pick/MemberPickServiceTest.java @@ -163,7 +163,7 @@ void findPicksMain() { Count pickPopularScore = new Count(0); String thumbnailUrl = "섬네일 이미지 url"; String author = "운영자"; - Pick pick = createPick(pickTitle, pickVoteTotalCount, pickViewTotalCount, pickCommentTotalCount, + Pick pick = createPick(member, pickTitle, pickVoteTotalCount, pickViewTotalCount, pickCommentTotalCount, pickPopularScore, thumbnailUrl, author, ContentStatus.APPROVAL); pick.changePopularScore(pickPopularScorePolicy); pickRepository.save(pick); @@ -208,6 +208,9 @@ void findPicksMain() { @DisplayName("회원이 커서 방식으로 회원 전용 조회수 내림차순으로 픽픽픽 메인을 조회한다.") void findPicksMainMOST_VIEWED() { // given + SocialMemberDto socialMemberDto = createSocialDto(userId, name, nickname, password, email, socialType, role); + Member member = Member.createMemberBy(socialMemberDto); + memberRepository.save(member); Title title1 = new Title("픽1타이틀"); Title title2 = new Title("픽2타이틀"); @@ -220,22 +223,18 @@ void findPicksMainMOST_VIEWED() { Count pick4ViewTotalCount = new Count(4); Count count = new Count(1); - Pick pick1 = createPick(title1, count, pick1ViewTotalCount, count, count, thumbnailUrl, author, + Pick pick1 = createPick(member, title1, count, pick1ViewTotalCount, count, count, thumbnailUrl, author, ContentStatus.APPROVAL); - Pick pick2 = createPick(title2, count, pick2ViewTotalCount, count, count, thumbnailUrl, author, + Pick pick2 = createPick(member, title2, count, pick2ViewTotalCount, count, count, thumbnailUrl, author, ContentStatus.APPROVAL); - Pick pick3 = createPick(title3, count, pick3ViewTotalCount, count, count, thumbnailUrl, author, + Pick pick3 = createPick(member, title3, count, pick3ViewTotalCount, count, count, thumbnailUrl, author, ContentStatus.REJECT); - Pick pick4 = createPick(title4, count, pick4ViewTotalCount, count, count, thumbnailUrl, author, + Pick pick4 = createPick(member, title4, count, pick4ViewTotalCount, count, count, thumbnailUrl, author, ContentStatus.READY); pickRepository.saveAll(List.of(pick1, pick2, pick3, pick4)); Pageable pageable = PageRequest.of(0, 10); - SocialMemberDto socialMemberDto = createSocialDto(userId, name, nickname, password, email, socialType, role); - Member member = Member.createMemberBy(socialMemberDto); - memberRepository.save(member); - UserPrincipal userPrincipal = UserPrincipal.createByMember(member); SecurityContext context = SecurityContextHolder.getContext(); context.setAuthentication(new OAuth2AuthenticationToken(userPrincipal, userPrincipal.getAuthorities(), @@ -259,25 +258,28 @@ void findPicksMainMOST_VIEWED() { @DisplayName("회원이 커서 방식으로 익명 사용자 전용 생성시간 내림차순으로 픽픽픽 메인을 조회한다.") void findPicksMainLATEST() { // given + SocialMemberDto socialMemberDto = createSocialDto(userId, name, nickname, password, email, socialType, role); + Member member = Member.createMemberBy(socialMemberDto); + memberRepository.save(member); + Title title1 = new Title("픽1타이틀"); Title title2 = new Title("픽2타이틀"); Title title3 = new Title("픽3타이틀"); Title title4 = new Title("픽4타이틀"); Count count = new Count(1); - Pick pick1 = createPick(title1, count, count, count, count, thumbnailUrl, author, ContentStatus.APPROVAL); - Pick pick2 = createPick(title2, count, count, count, count, thumbnailUrl, author, ContentStatus.APPROVAL); - Pick pick3 = createPick(title3, count, count, count, count, thumbnailUrl, author, ContentStatus.APPROVAL); - Pick pick4 = createPick(title4, count, count, count, count, thumbnailUrl, author, ContentStatus.READY); - Pick pick5 = createPick(title4, count, count, count, count, thumbnailUrl, author, ContentStatus.REJECT); + Pick pick1 = createPick(member, title1, count, count, count, count, thumbnailUrl, author, + ContentStatus.APPROVAL); + Pick pick2 = createPick(member, title2, count, count, count, count, thumbnailUrl, author, + ContentStatus.APPROVAL); + Pick pick3 = createPick(member, title3, count, count, count, count, thumbnailUrl, author, + ContentStatus.APPROVAL); + Pick pick4 = createPick(member, title4, count, count, count, count, thumbnailUrl, author, ContentStatus.READY); + Pick pick5 = createPick(member, title4, count, count, count, count, thumbnailUrl, author, ContentStatus.REJECT); pickRepository.saveAll(List.of(pick1, pick2, pick3, pick4, pick5)); Pageable pageable = PageRequest.of(0, 10); - SocialMemberDto socialMemberDto = createSocialDto(userId, name, nickname, password, email, socialType, role); - Member member = Member.createMemberBy(socialMemberDto); - memberRepository.save(member); - UserPrincipal userPrincipal = UserPrincipal.createByMember(member); SecurityContext context = SecurityContextHolder.getContext(); context.setAuthentication(new OAuth2AuthenticationToken(userPrincipal, userPrincipal.getAuthorities(), @@ -302,6 +304,10 @@ void findPicksMainLATEST() { @DisplayName("회원이 커서 방식으로 익명 사용자 전용 댓글수 내림차순으로 픽픽픽 메인을 조회한다.") void findPicksMainMOST_COMMENTED() { // given + SocialMemberDto socialMemberDto = createSocialDto(userId, name, nickname, password, email, socialType, role); + Member member = Member.createMemberBy(socialMemberDto); + memberRepository.save(member); + Title title1 = new Title("픽1타이틀"); Title title2 = new Title("픽2타이틀"); Title title3 = new Title("픽3타이틀"); @@ -312,24 +318,20 @@ void findPicksMainMOST_COMMENTED() { Count pick5commentTotalCount = new Count(5); Count count = new Count(1); - Pick pick1 = createPick(title1, count, count, pick1commentTotalCount, count, thumbnailUrl, author, + Pick pick1 = createPick(member, title1, count, count, pick1commentTotalCount, count, thumbnailUrl, author, ContentStatus.APPROVAL); - Pick pick2 = createPick(title2, count, count, pick2commentTotalCount, count, thumbnailUrl, author, + Pick pick2 = createPick(member, title2, count, count, pick2commentTotalCount, count, thumbnailUrl, author, ContentStatus.APPROVAL); - Pick pick3 = createPick(title3, count, count, pick3commentTotalCount, count, thumbnailUrl, author, + Pick pick3 = createPick(member, title3, count, count, pick3commentTotalCount, count, thumbnailUrl, author, ContentStatus.APPROVAL); - Pick pick4 = createPick(title3, count, count, pick4commentTotalCount, count, thumbnailUrl, author, + Pick pick4 = createPick(member, title3, count, count, pick4commentTotalCount, count, thumbnailUrl, author, ContentStatus.READY); - Pick pick5 = createPick(title3, count, count, pick5commentTotalCount, count, thumbnailUrl, author, + Pick pick5 = createPick(member, title3, count, count, pick5commentTotalCount, count, thumbnailUrl, author, ContentStatus.REJECT); pickRepository.saveAll(List.of(pick1, pick2, pick3, pick4, pick5)); Pageable pageable = PageRequest.of(0, 10); - SocialMemberDto socialMemberDto = createSocialDto(userId, name, nickname, password, email, socialType, role); - Member member = Member.createMemberBy(socialMemberDto); - memberRepository.save(member); - UserPrincipal userPrincipal = UserPrincipal.createByMember(member); SecurityContext context = SecurityContextHolder.getContext(); context.setAuthentication(new OAuth2AuthenticationToken(userPrincipal, userPrincipal.getAuthorities(), @@ -357,6 +359,10 @@ void findPicksMainMOST_COMMENTED() { + ", 조회수:" + PickPopularScorePolicy.VIEW_WEIGHT + ")") void findPicksMainPOPULAR() { // given + SocialMemberDto socialMemberDto = createSocialDto(userId, name, nickname, password, email, socialType, role); + Member member = Member.createMemberBy(socialMemberDto); + memberRepository.save(member); + Title title1 = new Title("픽1타이틀"); Title title2 = new Title("픽2타이틀"); Title title3 = new Title("픽3타이틀"); @@ -373,33 +379,34 @@ void findPicksMainPOPULAR() { Count pick3VoteTotalCount = new Count(1); Count pick3ViewTotalCount = new Count(2); - Pick pick1 = createPick(new Title("픽1타이틀"), pick1VoteTotalCount, pick1ViewTotalCount, pick1commentTotalCount, + Pick pick1 = createPick(member, new Title("픽1타이틀"), pick1VoteTotalCount, pick1ViewTotalCount, + pick1commentTotalCount, thumbnailUrl, author, ContentStatus.APPROVAL, List.of()); pick1.changePopularScore(pickPopularScorePolicy); - Pick pick2 = createPick(new Title("픽2타이틀"), pick2VoteTotalCount, pick2ViewTotalCount, pick2commentTotalCount, + Pick pick2 = createPick(member, new Title("픽2타이틀"), pick2VoteTotalCount, pick2ViewTotalCount, + pick2commentTotalCount, thumbnailUrl, author, ContentStatus.APPROVAL, List.of()); pick2.changePopularScore(pickPopularScorePolicy); - Pick pick3 = createPick(new Title("픽3타이틀"), pick3VoteTotalCount, pick3ViewTotalCount, pick3commentTotalCount, + Pick pick3 = createPick(member, new Title("픽3타이틀"), pick3VoteTotalCount, pick3ViewTotalCount, + pick3commentTotalCount, thumbnailUrl, author, ContentStatus.APPROVAL, List.of()); pick3.changePopularScore(pickPopularScorePolicy); - Pick pick4 = createPick(new Title("픽4타이틀"), pick3VoteTotalCount, pick3ViewTotalCount, pick3commentTotalCount, + Pick pick4 = createPick(member, new Title("픽4타이틀"), pick3VoteTotalCount, pick3ViewTotalCount, + pick3commentTotalCount, thumbnailUrl, author, ContentStatus.READY, List.of()); pick4.changePopularScore(pickPopularScorePolicy); - Pick pick5 = createPick(new Title("픽5타이틀"), pick3VoteTotalCount, pick3ViewTotalCount, pick3commentTotalCount, + Pick pick5 = createPick(member, new Title("픽5타이틀"), pick3VoteTotalCount, pick3ViewTotalCount, + pick3commentTotalCount, thumbnailUrl, author, ContentStatus.REJECT, List.of()); pick5.changePopularScore(pickPopularScorePolicy); pickRepository.saveAll(List.of(pick1, pick2, pick3, pick4, pick5)); Pageable pageable = PageRequest.of(0, 10); - SocialMemberDto socialMemberDto = createSocialDto(userId, name, nickname, password, email, socialType, role); - Member member = Member.createMemberBy(socialMemberDto); - memberRepository.save(member); - UserPrincipal userPrincipal = UserPrincipal.createByMember(member); SecurityContext context = SecurityContextHolder.getContext(); context.setAuthentication(new OAuth2AuthenticationToken(userPrincipal, userPrincipal.getAuthorities(), @@ -479,7 +486,7 @@ void uploadImagesNameException(String name) { } @Test - @DisplayName("일반 회원이 픽픽픽을 작성하면 픽픽픽은 대기(READY) 상태이다.") + @DisplayName("일반 회원이 픽픽픽을 작성하면 픽픽픽은 승인(APPROVAL) 상태이다.") void registerPickRoleUser() { // given SocialMemberDto socialMemberDto = createSocialDto(userId, name, nickname, password, email, socialType, role); @@ -524,7 +531,7 @@ void registerPickRoleUser() { assertAll( () -> assertThat(findPick.getTitle()).isEqualTo(new Title("나의 픽픽픽")), () -> assertThat(findPick.getAuthor()).isEqualTo(member.getName()), - () -> assertThat(findPick.getContentStatus()).isEqualTo(ContentStatus.READY), + () -> assertThat(findPick.getContentStatus()).isEqualTo(ContentStatus.APPROVAL), () -> assertThat(findPick.getPickOptions()).hasSize(2) .extracting("title", "contents") .containsAnyOf( @@ -1626,12 +1633,13 @@ private Pick createPick(Title title, Member member) { .build(); } - private Pick createPick(Title title, Count pickVoteTotalCount, Count pickViewTotalCount, + private Pick createPick(Member member, Title title, Count pickVoteTotalCount, Count pickViewTotalCount, Count pickcommentTotalCount, Count pickPopularScore, String thumbnailUrl, String author, ContentStatus contentStatus ) { return Pick.builder() + .member(member) .title(title) .voteTotalCount(pickVoteTotalCount) .viewTotalCount(pickViewTotalCount) @@ -1643,13 +1651,14 @@ private Pick createPick(Title title, Count pickVoteTotalCount, Count pickViewTot .build(); } - private Pick createPick(Title title, Count pickVoteTotalCount, Count pickViewTotalCount, + private Pick createPick(Member member, Title title, Count pickVoteTotalCount, Count pickViewTotalCount, Count pickcommentTotalCount, String thumbnailUrl, String author, ContentStatus contentStatus, List pickVotes ) { Pick pick = Pick.builder() + .member(member) .title(title) .voteTotalCount(pickVoteTotalCount) .viewTotalCount(pickViewTotalCount) diff --git a/src/test/java/com/dreamypatisiel/devdevdev/domain/service/techArticle/GuestTechArticleServiceTest.java b/src/test/java/com/dreamypatisiel/devdevdev/domain/service/techArticle/GuestTechArticleServiceTest.java index 8a6fc10a..4d4080ca 100644 --- a/src/test/java/com/dreamypatisiel/devdevdev/domain/service/techArticle/GuestTechArticleServiceTest.java +++ b/src/test/java/com/dreamypatisiel/devdevdev/domain/service/techArticle/GuestTechArticleServiceTest.java @@ -6,14 +6,20 @@ import static com.dreamypatisiel.devdevdev.domain.service.techArticle.techArticle.GuestTechArticleService.INVALID_ANONYMOUS_CAN_NOT_USE_THIS_FUNCTION_MESSAGE; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import com.dreamypatisiel.devdevdev.domain.entity.AnonymousMember; import com.dreamypatisiel.devdevdev.domain.entity.TechArticle; +import com.dreamypatisiel.devdevdev.domain.entity.TechArticleRecommend; import com.dreamypatisiel.devdevdev.domain.entity.embedded.Count; +import com.dreamypatisiel.devdevdev.domain.entity.embedded.Title; import com.dreamypatisiel.devdevdev.domain.entity.embedded.Url; import com.dreamypatisiel.devdevdev.domain.entity.enums.Role; import com.dreamypatisiel.devdevdev.domain.entity.enums.SocialType; +import com.dreamypatisiel.devdevdev.domain.repository.techArticle.TechArticleRecommendRepository; import com.dreamypatisiel.devdevdev.domain.repository.techArticle.TechArticleRepository; +import com.dreamypatisiel.devdevdev.domain.service.member.AnonymousMemberService; import com.dreamypatisiel.devdevdev.domain.service.techArticle.techArticle.GuestTechArticleService; import com.dreamypatisiel.devdevdev.elastic.domain.service.ElasticsearchSupportTest; import com.dreamypatisiel.devdevdev.exception.NotFoundException; @@ -22,6 +28,7 @@ import com.dreamypatisiel.devdevdev.global.utils.AuthenticationMemberUtils; import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.TechArticleDetailResponse; import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.TechArticleMainResponse; +import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.TechArticleRecommendResponse; import jakarta.persistence.EntityManager; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -36,6 +43,9 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; +import java.util.List; +import java.util.Optional; + class GuestTechArticleServiceTest extends ElasticsearchSupportTest { @Autowired @@ -43,6 +53,10 @@ class GuestTechArticleServiceTest extends ElasticsearchSupportTest { @Autowired TechArticleRepository techArticleRepository; @Autowired + TechArticleRecommendRepository techArticleRecommendRepository; + @Autowired + AnonymousMemberService anonymousMemberService; + @Autowired EntityManager em; @Mock Authentication authentication; @@ -102,7 +116,7 @@ void getTechArticle() { SecurityContextHolder.setContext(securityContext); // when - TechArticleDetailResponse techArticleDetailResponse = guestTechArticleService.getTechArticle(id, + TechArticleDetailResponse techArticleDetailResponse = guestTechArticleService.getTechArticle(id, null, authentication); // then @@ -114,6 +128,38 @@ void getTechArticle() { }); } + @Test + @DisplayName("익명 사용자가 본인이 추천했던 기술블로그 상세를 조회한다. 이때 추천 값은 true 이다.") + void getTechArticleWithRecommend() { + // given + Authentication authentication = mock(Authentication.class); + when(authentication.getPrincipal()).thenReturn(AuthenticationMemberUtils.ANONYMOUS_USER); + when(securityContext.getAuthentication()).thenReturn(authentication); + SecurityContextHolder.setContext(securityContext); + + String anonymousMemberId = "GA1.1.276672604.1715872960"; + Long techArticleId = firstTechArticle.getId(); + + AnonymousMember anonymousMember = anonymousMemberService.findOrCreateAnonymousMember(anonymousMemberId); + TechArticleRecommend techArticleRecommend = TechArticleRecommend.create(anonymousMember, firstTechArticle); + techArticleRecommendRepository.save(techArticleRecommend); + + em.flush(); + em.clear(); + + // when + TechArticleDetailResponse techArticleDetailResponse = guestTechArticleService.getTechArticle(techArticleId, anonymousMemberId, + authentication); + + // then + assertThat(techArticleDetailResponse) + .isNotNull() + .isInstanceOf(TechArticleDetailResponse.class) + .satisfies(article -> { + assertThat(article.getIsRecommended()).isTrue(); + }); + } + @Test @DisplayName("익명 사용자가 기술블로그 상세를 조회하면 조회수가 1 증가한다.") void getTechArticleIncrementViewCount() { @@ -127,7 +173,7 @@ void getTechArticleIncrementViewCount() { SecurityContextHolder.setContext(securityContext); // when - TechArticleDetailResponse techArticleDetailResponse = guestTechArticleService.getTechArticle(id, + TechArticleDetailResponse techArticleDetailResponse = guestTechArticleService.getTechArticle(id, null, authentication); // then @@ -151,7 +197,7 @@ void getTechArticleNotAnonymousUserException() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); // when // then - assertThatThrownBy(() -> guestTechArticleService.getTechArticle(id, authentication)) + assertThatThrownBy(() -> guestTechArticleService.getTechArticle(id, null, authentication)) .isInstanceOf(IllegalStateException.class) .hasMessage(AuthenticationMemberUtils.INVALID_METHODS_CALL_MESSAGE); } @@ -160,7 +206,9 @@ void getTechArticleNotAnonymousUserException() { @DisplayName("익명 사용자가 기술블로그 상세를 조회할 때 기술블로그가 존재하지 않으면 예외가 발생한다.") void getTechArticleNotFoundTechArticleException() { // given - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle( + new Title("매일 1,000만 사용자의 데이터를 꿈파는 어떻게 처리할까?"), + new Url("https://example.com"), new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); @@ -172,7 +220,7 @@ void getTechArticleNotFoundTechArticleException() { SecurityContextHolder.setContext(securityContext); // when // then - assertThatThrownBy(() -> guestTechArticleService.getTechArticle(id, authentication)) + assertThatThrownBy(() -> guestTechArticleService.getTechArticle(id, null, authentication)) .isInstanceOf(NotFoundException.class) .hasMessage(NOT_FOUND_TECH_ARTICLE_MESSAGE); } @@ -181,7 +229,9 @@ void getTechArticleNotFoundTechArticleException() { @DisplayName("익명 사용자가 기술블로그 상세를 조회할 때 엘라스틱ID가 존재하지 않으면 예외가 발생한다.") void getTechArticleNotFoundElasticIdException() { // given - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle( + new Title("매일 1,000만 사용자의 데이터를 꿈파는 어떻게 처리할까?"), + new Url("https://example.com"), new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); @@ -193,7 +243,7 @@ void getTechArticleNotFoundElasticIdException() { SecurityContextHolder.setContext(securityContext); // when // then - assertThatThrownBy(() -> guestTechArticleService.getTechArticle(id, authentication)) + assertThatThrownBy(() -> guestTechArticleService.getTechArticle(id, null, authentication)) .isInstanceOf(TechArticleException.class) .hasMessage(NOT_FOUND_ELASTIC_ID_MESSAGE); } @@ -202,7 +252,9 @@ void getTechArticleNotFoundElasticIdException() { @DisplayName("익명 사용자가 기술블로그 상세를 조회할 때 엘라스틱 기술블로그가 존재하지 않으면 예외가 발생한다.") void getTechArticleNotFoundElasticTechArticleException() { // given - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle( + new Title("매일 1,000만 사용자의 데이터를 꿈파는 어떻게 처리할까?"), + new Url("https://example.com"), new Count(1L), new Count(1L), new Count(1L), new Count(1L), "elasticId", company); @@ -214,7 +266,7 @@ void getTechArticleNotFoundElasticTechArticleException() { SecurityContextHolder.setContext(securityContext); // when // then - assertThatThrownBy(() -> guestTechArticleService.getTechArticle(id, authentication)) + assertThatThrownBy(() -> guestTechArticleService.getTechArticle(id, null, authentication)) .isInstanceOf(NotFoundException.class) .hasMessage(NOT_FOUND_ELASTIC_TECH_ARTICLE_MESSAGE); } @@ -234,4 +286,96 @@ void updateBookmarkAccessDeniedException() { .isInstanceOf(AccessDeniedException.class) .hasMessage(INVALID_ANONYMOUS_CAN_NOT_USE_THIS_FUNCTION_MESSAGE); } + + @Test + @DisplayName("익명 사용자가 기술블로그를 추천하면 새로운 추천이 생성되고 기술블로그의 점수가 변경된다.") + void createTechArticleRecommend() { + // given + Authentication authentication = mock(Authentication.class); + when(authentication.getPrincipal()).thenReturn(AuthenticationMemberUtils.ANONYMOUS_USER); + when(securityContext.getAuthentication()).thenReturn(authentication); + SecurityContextHolder.setContext(securityContext); + + String anonymousMemberId = "GA1.1.276672604.1715872960"; + Long techArticleId = firstTechArticle.getId(); + Count popularScore = firstTechArticle.getPopularScore(); + Count recommendTotalCount = firstTechArticle.getRecommendTotalCount(); + + // when + TechArticleRecommendResponse techArticleRecommendResponse = guestTechArticleService.updateRecommend(techArticleId, anonymousMemberId, authentication); + + // then + assertThat(techArticleRecommendResponse) + .isNotNull() + .satisfies(response -> { + assertThat(response.getTechArticleId()).isEqualTo(techArticleId); + assertThat(response.getStatus()).isTrue(); + assertThat(response.getRecommendTotalCount()).isEqualTo(recommendTotalCount.getCount() + 1); + }); + + TechArticle techArticle = techArticleRepository.findById(techArticleId).get(); + assertThat(techArticle) + .satisfies(article -> { + assertThat(article.getRecommendTotalCount().getCount()).isEqualTo(recommendTotalCount.getCount() + 1); + assertThat(article.getPopularScore().getCount()).isEqualTo(popularScore.getCount() + 4); + }); + + AnonymousMember anonymousMember = anonymousMemberService.findOrCreateAnonymousMember(anonymousMemberId); + TechArticleRecommend techArticleRecommend = techArticleRecommendRepository.findByTechArticleAndAnonymousMember(firstTechArticle, anonymousMember).get(); + assertThat(techArticleRecommend) + .satisfies(recommend -> { + assertThat(recommend.getTechArticle().getId()).isEqualTo(firstTechArticle.getId()); + assertThat(recommend.getAnonymousMember()).isEqualTo(anonymousMember); + assertThat(recommend.isRecommended()).isTrue(); + }); + } + + @Test + @DisplayName("익명 사용자가 기술블로그 추천을 취소하면 상태가 false로 바뀌고 기술블로그의 점수가 변경된다.") + void cancelTechArticleRecommend() { + // given + Authentication authentication = mock(Authentication.class); + when(authentication.getPrincipal()).thenReturn(AuthenticationMemberUtils.ANONYMOUS_USER); + when(securityContext.getAuthentication()).thenReturn(authentication); + SecurityContextHolder.setContext(securityContext); + + String anonymousMemberId = "GA1.1.276672604.1715872960"; + Long techArticleId = firstTechArticle.getId(); + Count popularScore = firstTechArticle.getPopularScore(); + Count recommendTotalCount = firstTechArticle.getRecommendTotalCount(); + + AnonymousMember anonymousMember = anonymousMemberService.findOrCreateAnonymousMember(anonymousMemberId); + TechArticleRecommend techArticleRecommend = TechArticleRecommend.create(anonymousMember, firstTechArticle); + techArticleRecommendRepository.save(techArticleRecommend); + + // when + TechArticleRecommendResponse techArticleRecommendResponse = guestTechArticleService.updateRecommend(techArticleId, anonymousMemberId, authentication); + + // then + em.flush(); + em.clear(); + + assertThat(techArticleRecommendResponse) + .isNotNull() + .satisfies(response -> { + assertThat(response.getTechArticleId()).isEqualTo(techArticleId); + assertThat(response.getStatus()).isFalse(); + }); + + TechArticle techArticle = techArticleRepository.findById(techArticleId).get(); + assertThat(techArticle) + .satisfies(article -> { + assertThat(article.getRecommendTotalCount().getCount()).isEqualTo(recommendTotalCount.getCount() - 1L); + assertThat(article.getPopularScore().getCount()).isEqualTo(popularScore.getCount() - 4L); + }); + + AnonymousMember findAnonymousMember = anonymousMemberService.findOrCreateAnonymousMember(anonymousMemberId); + TechArticleRecommend findTechArticleRecommend = techArticleRecommendRepository.findByTechArticleAndAnonymousMember(firstTechArticle, findAnonymousMember).get(); + assertThat(findTechArticleRecommend) + .satisfies(recommend -> { + assertThat(recommend.getTechArticle().getId()).isEqualTo(firstTechArticle.getId()); + assertThat(recommend.getAnonymousMember()).isEqualTo(findAnonymousMember); + assertThat(recommend.isRecommended()).isFalse(); + }); + } } \ No newline at end of file diff --git a/src/test/java/com/dreamypatisiel/devdevdev/domain/service/techArticle/GuestTechCommentServiceTest.java b/src/test/java/com/dreamypatisiel/devdevdev/domain/service/techArticle/GuestTechCommentServiceTest.java index 79d7b2e6..1618d4f7 100644 --- a/src/test/java/com/dreamypatisiel/devdevdev/domain/service/techArticle/GuestTechCommentServiceTest.java +++ b/src/test/java/com/dreamypatisiel/devdevdev/domain/service/techArticle/GuestTechCommentServiceTest.java @@ -15,6 +15,7 @@ import com.dreamypatisiel.devdevdev.domain.entity.embedded.CommentContents; import com.dreamypatisiel.devdevdev.domain.entity.embedded.CompanyName; import com.dreamypatisiel.devdevdev.domain.entity.embedded.Count; +import com.dreamypatisiel.devdevdev.domain.entity.embedded.Title; import com.dreamypatisiel.devdevdev.domain.entity.embedded.Url; import com.dreamypatisiel.devdevdev.domain.entity.enums.Role; import com.dreamypatisiel.devdevdev.domain.entity.enums.SocialType; @@ -30,7 +31,6 @@ import com.dreamypatisiel.devdevdev.global.security.oauth2.model.UserPrincipal; import com.dreamypatisiel.devdevdev.global.utils.AuthenticationMemberUtils; import com.dreamypatisiel.devdevdev.web.dto.SliceCommentCustom; -import com.dreamypatisiel.devdevdev.web.dto.SliceCustom; import com.dreamypatisiel.devdevdev.web.dto.request.techArticle.RegisterTechCommentRequest; import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.TechCommentsResponse; import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.TechRepliedCommentsResponse; @@ -101,7 +101,8 @@ void registerTechComment() { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); TechArticle savedTechArticle = techArticleRepository.save(techArticle); Long id = savedTechArticle.getId(); @@ -128,7 +129,8 @@ void registerRepliedTechComment() { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -160,7 +162,8 @@ void recommendTechComment() { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(2L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -189,7 +192,8 @@ void getTechCommentsSortByOLDEST() { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(12L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -481,7 +485,8 @@ void getTechCommentsSortByLATEST() { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(12L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -608,7 +613,7 @@ techArticle, originParentTechComment2, originParentTechComment2, new Count(0L), false, false, false, - false ) + false) ); TechCommentsResponse techCommentsResponse6 = response.getContent().get(0); @@ -648,7 +653,8 @@ void getTechCommentsSortByMostCommented() { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(12L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -940,7 +946,8 @@ void getTechCommentsSortByMostRecommended() { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(12L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -1086,8 +1093,8 @@ void getTechCommentsByCursor() { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), - new Count(1L), new Count(12L), new Count(1L), null, company); + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(12L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -1261,8 +1268,8 @@ void findTechBestComments() { companyRepository.save(company); // 기술 블로그 생성 - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), - new Count(1L), new Count(12L), new Count(1L), null, company); + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(12L), new Count(1L), null, company); techArticleRepository.save(techArticle); // 댓글 생성 diff --git a/src/test/java/com/dreamypatisiel/devdevdev/domain/service/techArticle/MemberTechArticleServiceTest.java b/src/test/java/com/dreamypatisiel/devdevdev/domain/service/techArticle/MemberTechArticleServiceTest.java index 2619b24d..ff70ca5f 100644 --- a/src/test/java/com/dreamypatisiel/devdevdev/domain/service/techArticle/MemberTechArticleServiceTest.java +++ b/src/test/java/com/dreamypatisiel/devdevdev/domain/service/techArticle/MemberTechArticleServiceTest.java @@ -6,16 +6,18 @@ import static com.dreamypatisiel.devdevdev.domain.exception.TechArticleExceptionMessage.NOT_FOUND_TECH_ARTICLE_MESSAGE; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; -import com.dreamypatisiel.devdevdev.domain.entity.Bookmark; -import com.dreamypatisiel.devdevdev.domain.entity.Member; -import com.dreamypatisiel.devdevdev.domain.entity.TechArticle; +import com.dreamypatisiel.devdevdev.domain.entity.*; import com.dreamypatisiel.devdevdev.domain.entity.embedded.Count; +import com.dreamypatisiel.devdevdev.domain.entity.embedded.Title; import com.dreamypatisiel.devdevdev.domain.entity.embedded.Url; import com.dreamypatisiel.devdevdev.domain.entity.enums.Role; import com.dreamypatisiel.devdevdev.domain.entity.enums.SocialType; -import com.dreamypatisiel.devdevdev.domain.repository.BookmarkRepository; +import com.dreamypatisiel.devdevdev.domain.repository.techArticle.BookmarkRepository; import com.dreamypatisiel.devdevdev.domain.repository.member.MemberRepository; +import com.dreamypatisiel.devdevdev.domain.repository.techArticle.TechArticleRecommendRepository; import com.dreamypatisiel.devdevdev.domain.repository.techArticle.TechArticleRepository; import com.dreamypatisiel.devdevdev.domain.service.techArticle.techArticle.MemberTechArticleService; import com.dreamypatisiel.devdevdev.elastic.domain.service.ElasticsearchSupportTest; @@ -24,9 +26,11 @@ import com.dreamypatisiel.devdevdev.exception.TechArticleException; import com.dreamypatisiel.devdevdev.global.security.oauth2.model.SocialMemberDto; import com.dreamypatisiel.devdevdev.global.security.oauth2.model.UserPrincipal; +import com.dreamypatisiel.devdevdev.global.utils.AuthenticationMemberUtils; import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.BookmarkResponse; import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.TechArticleDetailResponse; import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.TechArticleMainResponse; +import com.dreamypatisiel.devdevdev.web.dto.response.techArticle.TechArticleRecommendResponse; import jakarta.persistence.EntityManager; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -50,6 +54,8 @@ class MemberTechArticleServiceTest extends ElasticsearchSupportTest { @Autowired BookmarkRepository bookmarkRepository; @Autowired + TechArticleRecommendRepository techArticleRecommendRepository; + @Autowired EntityManager em; String userId = "dreamy5patisiel"; @@ -122,7 +128,69 @@ void getTechArticle() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); // when - TechArticleDetailResponse techArticleDetailResponse = memberTechArticleService.getTechArticle(id, + TechArticleDetailResponse techArticleDetailResponse = memberTechArticleService.getTechArticle(id, null, + authentication); + + // then + assertThat(techArticleDetailResponse) + .isNotNull() + .isInstanceOf(TechArticleDetailResponse.class) + .satisfies(article -> { + assertThat(article.getIsBookmarked()).isNotNull(); + }); + } + + @Test + @DisplayName("회원이 기술블로그 상세를 조회할 때 기술블로그를 추천한 이력이 있으면 추천이 true이다.") + void getTechArticleWithRecommend() { + // given + Long id = firstTechArticle.getId(); + + SocialMemberDto socialMemberDto = createSocialDto(userId, name, nickname, password, email, socialType, role); + Member member = Member.createMemberBy(socialMemberDto); + memberRepository.save(member); + + UserPrincipal userPrincipal = UserPrincipal.createByMember(member); + SecurityContext context = SecurityContextHolder.getContext(); + context.setAuthentication(new OAuth2AuthenticationToken(userPrincipal, userPrincipal.getAuthorities(), + userPrincipal.getSocialType().name())); + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + TechArticleRecommend techArticleRecommend = TechArticleRecommend.create(member, firstTechArticle); + techArticleRecommendRepository.save(techArticleRecommend); + + // when + TechArticleDetailResponse techArticleDetailResponse = memberTechArticleService.getTechArticle(id, null, + authentication); + + // then + assertThat(techArticleDetailResponse) + .isNotNull() + .isInstanceOf(TechArticleDetailResponse.class) + .satisfies(article -> { + assertThat(article.getIsBookmarked()).isNotNull(); + assertThat(article.getIsRecommended()).isTrue(); + }); + } + + @Test + @DisplayName("회원이 기술블로그 상세를 조회할 때 기술블로그를 추천한 이력이 없으면 추천이 false이다.") + void getTechArticleWithoutRecommend() { + // given + Long id = firstTechArticle.getId(); + + SocialMemberDto socialMemberDto = createSocialDto(userId, name, nickname, password, email, socialType, role); + Member member = Member.createMemberBy(socialMemberDto); + memberRepository.save(member); + + UserPrincipal userPrincipal = UserPrincipal.createByMember(member); + SecurityContext context = SecurityContextHolder.getContext(); + context.setAuthentication(new OAuth2AuthenticationToken(userPrincipal, userPrincipal.getAuthorities(), + userPrincipal.getSocialType().name())); + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + // when + TechArticleDetailResponse techArticleDetailResponse = memberTechArticleService.getTechArticle(id, null, authentication); // then @@ -131,6 +199,7 @@ void getTechArticle() { .isInstanceOf(TechArticleDetailResponse.class) .satisfies(article -> { assertThat(article.getIsBookmarked()).isNotNull(); + assertThat(article.getIsRecommended()).isFalse(); }); } @@ -153,7 +222,7 @@ void getTechArticleIncrementViewCount() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); // when - TechArticleDetailResponse techArticleDetailResponse = memberTechArticleService.getTechArticle(id, + TechArticleDetailResponse techArticleDetailResponse = memberTechArticleService.getTechArticle(id, null, authentication); // then @@ -179,7 +248,7 @@ void getTechArticleNotFoundMemberException() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); // when // then - assertThatThrownBy(() -> memberTechArticleService.getTechArticle(id, authentication)) + assertThatThrownBy(() -> memberTechArticleService.getTechArticle(id, null, authentication)) .isInstanceOf(MemberException.class) .hasMessage(INVALID_MEMBER_NOT_FOUND_MESSAGE); } @@ -188,7 +257,8 @@ void getTechArticleNotFoundMemberException() { @DisplayName("회원이 기술블로그 상세를 조회할 때 기술블로그가 존재하지 않으면 예외가 발생한다.") void getTechArticleNotFoundTechArticleException() { // given - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); @@ -206,7 +276,7 @@ void getTechArticleNotFoundTechArticleException() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); // when // then - assertThatThrownBy(() -> memberTechArticleService.getTechArticle(id, authentication)) + assertThatThrownBy(() -> memberTechArticleService.getTechArticle(id, null, authentication)) .isInstanceOf(NotFoundException.class) .hasMessage(NOT_FOUND_TECH_ARTICLE_MESSAGE); } @@ -215,7 +285,8 @@ void getTechArticleNotFoundTechArticleException() { @DisplayName("회원이 기술블로그 상세를 조회할 때 엘라스틱ID가 존재하지 않으면 예외가 발생한다.") void getTechArticleNotFoundElasticIdException() { // given - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); @@ -233,7 +304,7 @@ void getTechArticleNotFoundElasticIdException() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); // when // then - assertThatThrownBy(() -> memberTechArticleService.getTechArticle(id, authentication)) + assertThatThrownBy(() -> memberTechArticleService.getTechArticle(id, null, authentication)) .isInstanceOf(TechArticleException.class) .hasMessage(NOT_FOUND_ELASTIC_ID_MESSAGE); } @@ -242,7 +313,8 @@ void getTechArticleNotFoundElasticIdException() { @DisplayName("회원이 기술블로그 상세를 조회할 때 엘라스틱 기술블로그가 존재하지 않으면 예외가 발생한다.") void getTechArticleNotFoundElasticTechArticleException() { // given - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), "elasticId", company); @@ -260,7 +332,7 @@ void getTechArticleNotFoundElasticTechArticleException() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); // when // then - assertThatThrownBy(() -> memberTechArticleService.getTechArticle(id, authentication)) + assertThatThrownBy(() -> memberTechArticleService.getTechArticle(id, null, authentication)) .isInstanceOf(NotFoundException.class) .hasMessage(NOT_FOUND_ELASTIC_TECH_ARTICLE_MESSAGE); } @@ -321,6 +393,100 @@ void updateBookmark() { .containsExactly(id, false); } + @Test + @DisplayName("회원이 기술블로그를 추천하면 새로운 추천이 생성되고 기술블로그의 점수가 변경된다.") + void createTechArticleRecommend() { + // given + SocialMemberDto socialMemberDto = createSocialDto(userId, name, nickname, password, email, socialType, role); + Member member = Member.createMemberBy(socialMemberDto); + memberRepository.save(member); + + UserPrincipal userPrincipal = UserPrincipal.createByMember(member); + SecurityContext context = SecurityContextHolder.getContext(); + context.setAuthentication(new OAuth2AuthenticationToken(userPrincipal, userPrincipal.getAuthorities(), + userPrincipal.getSocialType().name())); + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + Long techArticleId = firstTechArticle.getId(); + Count popularScore = firstTechArticle.getPopularScore(); + Count recommendTotalCount = firstTechArticle.getRecommendTotalCount(); + + // when + TechArticleRecommendResponse techArticleRecommendResponse = memberTechArticleService.updateRecommend(techArticleId, null, authentication); + + // then + assertThat(techArticleRecommendResponse) + .isNotNull() + .satisfies(response -> { + assertThat(response.getTechArticleId()).isEqualTo(techArticleId); + assertThat(response.getStatus()).isTrue(); + }); + + TechArticle techArticle = techArticleRepository.findById(techArticleId).get(); + assertThat(techArticle) + .satisfies(article -> { + assertThat(article.getRecommendTotalCount().getCount()).isEqualTo(recommendTotalCount.getCount() + 1); + assertThat(article.getPopularScore().getCount()).isEqualTo(popularScore.getCount() + 4); + }); + + TechArticleRecommend techArticleRecommend = techArticleRecommendRepository.findByTechArticleAndMember(firstTechArticle, member).get(); + assertThat(techArticleRecommend) + .satisfies(recommend -> { + assertThat(recommend.getTechArticle().getId()).isEqualTo(firstTechArticle.getId()); + assertThat(recommend.getMember()).isEqualTo(member); + assertThat(recommend.isRecommended()).isTrue(); + }); + } + + @Test + @DisplayName("익명 사용자가 기술블로그 추천을 취소하면 상태가 false로 바뀌고 기술블로그의 점수가 변경된다.") + void cancelTechArticleRecommend() { + // given + SocialMemberDto socialMemberDto = createSocialDto(userId, name, nickname, password, email, socialType, role); + Member member = Member.createMemberBy(socialMemberDto); + memberRepository.save(member); + + UserPrincipal userPrincipal = UserPrincipal.createByMember(member); + SecurityContext context = SecurityContextHolder.getContext(); + context.setAuthentication(new OAuth2AuthenticationToken(userPrincipal, userPrincipal.getAuthorities(), + userPrincipal.getSocialType().name())); + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + Long techArticleId = firstTechArticle.getId(); + Count popularScore = firstTechArticle.getPopularScore(); + Count recommendTotalCount = firstTechArticle.getRecommendTotalCount(); + + TechArticleRecommend techArticleRecommend = TechArticleRecommend.create(member, firstTechArticle); + techArticleRecommendRepository.save(techArticleRecommend); + + // when + TechArticleRecommendResponse techArticleRecommendResponse = memberTechArticleService.updateRecommend(techArticleId, null, authentication); + + // then + assertThat(techArticleRecommendResponse) + .isNotNull() + .satisfies(response -> { + assertThat(response.getTechArticleId()).isEqualTo(techArticleId); + assertThat(response.getStatus()).isFalse(); + assertThat(response.getRecommendTotalCount()).isEqualTo(recommendTotalCount.getCount() - 1); + }); + + TechArticle techArticle = techArticleRepository.findById(techArticleId).get(); + assertThat(techArticle) + .satisfies(article -> { + assertThat(article.getRecommendTotalCount().getCount()).isEqualTo(recommendTotalCount.getCount() - 1L); + assertThat(article.getPopularScore().getCount()).isEqualTo(popularScore.getCount() - 4L); + }); + + TechArticleRecommend findTechArticleRecommend = techArticleRecommendRepository.findByTechArticleAndMember(firstTechArticle, member).get(); + assertThat(findTechArticleRecommend) + .satisfies(recommend -> { + assertThat(recommend.getTechArticle().getId()).isEqualTo(firstTechArticle.getId()); + assertThat(recommend.getMember()).isEqualTo(member); + assertThat(recommend.isRecommended()).isFalse(); + }); + } + private SocialMemberDto createSocialDto(String userId, String name, String nickName, String password, String email, String socialType, String role) { return SocialMemberDto.builder() diff --git a/src/test/java/com/dreamypatisiel/devdevdev/domain/service/techArticle/MemberTechCommentServiceTest.java b/src/test/java/com/dreamypatisiel/devdevdev/domain/service/techArticle/MemberTechCommentServiceTest.java index 5cc86d95..3862d28e 100644 --- a/src/test/java/com/dreamypatisiel/devdevdev/domain/service/techArticle/MemberTechCommentServiceTest.java +++ b/src/test/java/com/dreamypatisiel/devdevdev/domain/service/techArticle/MemberTechCommentServiceTest.java @@ -20,6 +20,7 @@ import com.dreamypatisiel.devdevdev.domain.entity.embedded.CommentContents; import com.dreamypatisiel.devdevdev.domain.entity.embedded.CompanyName; import com.dreamypatisiel.devdevdev.domain.entity.embedded.Count; +import com.dreamypatisiel.devdevdev.domain.entity.embedded.Title; import com.dreamypatisiel.devdevdev.domain.entity.embedded.Url; import com.dreamypatisiel.devdevdev.domain.entity.enums.Role; import com.dreamypatisiel.devdevdev.domain.entity.enums.SocialType; @@ -46,7 +47,6 @@ import com.dreamypatisiel.devdevdev.web.dto.util.CommonResponseUtil; import jakarta.persistence.EntityManager; import java.time.LocalDateTime; -import java.util.Date; import java.util.List; import org.assertj.core.groups.Tuple; import org.junit.jupiter.api.DisplayName; @@ -122,7 +122,8 @@ void registerTechComment() { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); TechArticle savedTechArticle = techArticleRepository.save(techArticle); Long id = savedTechArticle.getId(); @@ -160,7 +161,8 @@ void registerTechCommentNotFoundTechArticleException() { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); TechArticle savedTechArticle = techArticleRepository.save(techArticle); Long id = savedTechArticle.getId() + 1; @@ -221,7 +223,8 @@ void modifyTechComment() { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -297,7 +300,8 @@ void modifyTechCommentNotFoundTechArticleCommentException() { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -330,7 +334,8 @@ void modifyTechCommentAlreadyDeletedException() { userPrincipal.getSocialType().name())); Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -370,7 +375,8 @@ void deleteTechComment() { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -415,7 +421,8 @@ void deleteTechCommentAlreadyDeletedException() { userPrincipal.getSocialType().name())); Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -453,7 +460,8 @@ void deleteTechCommentNotFoundException() { userPrincipal.getSocialType().name())); Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -487,7 +495,8 @@ void deleteTechCommentAdmin() { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -536,7 +545,8 @@ void deleteTechCommentNotByMemberException() { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -589,7 +599,8 @@ void registerRepliedTechComment() { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -647,7 +658,8 @@ void registerRepliedTechCommentToRepliedTechComment() { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(2L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -710,7 +722,8 @@ void registerRepliedTechCommentNotFoundTechCommentException() { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -746,7 +759,8 @@ void registerRepliedTechCommentDeletedTechCommentException() { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -799,7 +813,7 @@ void registerRepliedTechCommentNotFoundMemberException() { @Test @DisplayName("회원은 커서 방식으로 기술블로그 댓글/답글을 조회할 수 있다. (등록순)") - void getTechCommentsSortByOLDEST() { + void getTechCommentsSortByOLDEST() { // given SocialMemberDto socialMemberDto = createSocialDto(userId, name, nickname, password, email, socialType, role); Member member = Member.createMemberBy(socialMemberDto); @@ -814,7 +828,8 @@ void getTechCommentsSortByOLDEST() { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(12L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -1109,7 +1124,8 @@ void getTechCommentsSortByLATEST() { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(12L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -1236,7 +1252,7 @@ techArticle, originParentTechComment2, originParentTechComment2, new Count(0L), true, false, false, - false ) + false) ); TechCommentsResponse techCommentsResponse6 = response.getContent().get(0); @@ -1279,7 +1295,8 @@ void getTechCommentsSortByMostCommented() { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(12L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -1574,7 +1591,8 @@ void getTechCommentsSortByMostRecommended() { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(12L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -1723,7 +1741,8 @@ void getTechCommentsByCursor() { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(12L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -1874,7 +1893,8 @@ void recommendTechComment() { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(2L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -1910,7 +1930,8 @@ void recommendTechCommentCancel() { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(2L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -1949,7 +1970,8 @@ void recommendTechCommentNotFoundTechCommentException() { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -1982,7 +2004,8 @@ void recommendTechCommentDeletedTechCommentException() { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -2071,7 +2094,8 @@ void findTechBestComments() { companyRepository.save(company); // 기술 블로그 생성 - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(12L), new Count(1L), null, company); techArticleRepository.save(techArticle); @@ -2219,7 +2243,8 @@ void findTechBestCommentsExcludeLessThanOneRecommend() { companyRepository.save(company); // 기술 블로그 생성 - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(12L), new Count(1L), null, company); techArticleRepository.save(techArticle); diff --git a/src/test/java/com/dreamypatisiel/devdevdev/global/security/oauth2/handler/OAuth2SuccessHandlerTest.java b/src/test/java/com/dreamypatisiel/devdevdev/global/security/oauth2/handler/OAuth2SuccessHandlerTest.java index 5016cb99..17cfbae9 100644 --- a/src/test/java/com/dreamypatisiel/devdevdev/global/security/oauth2/handler/OAuth2SuccessHandlerTest.java +++ b/src/test/java/com/dreamypatisiel/devdevdev/global/security/oauth2/handler/OAuth2SuccessHandlerTest.java @@ -104,13 +104,15 @@ void onAuthenticationSuccess() throws IOException { Cookie loginStatusCookie = response.getCookie(JwtCookieConstant.DEVDEVDEV_LOGIN_STATUS); Cookie nicknameCookie = response.getCookie(JwtCookieConstant.DEVDEVDEV_MEMBER_NICKNAME); Cookie emailCookie = response.getCookie(JwtCookieConstant.DEVDEVDEV_MEMBER_EMAIL); + Cookie isAdmin = response.getCookie(JwtCookieConstant.DEVDEVDEV_MEMBER_IS_ADMIN); assertAll( () -> assertThat(accessCookie).isNotNull(), () -> assertThat(refreshCookie).isNotNull(), () -> assertThat(loginStatusCookie).isNotNull(), () -> assertThat(nicknameCookie).isNotNull(), - () -> assertThat(emailCookie).isNotNull() + () -> assertThat(emailCookie).isNotNull(), + () -> assertThat(isAdmin).isNotNull() ); assertAll( () -> assertThat(accessCookie.isHttpOnly()).isFalse(), diff --git a/src/test/java/com/dreamypatisiel/devdevdev/global/utils/CookieUtilsTest.java b/src/test/java/com/dreamypatisiel/devdevdev/global/utils/CookieUtilsTest.java index 1bdf24e0..0019e798 100644 --- a/src/test/java/com/dreamypatisiel/devdevdev/global/utils/CookieUtilsTest.java +++ b/src/test/java/com/dreamypatisiel/devdevdev/global/utils/CookieUtilsTest.java @@ -17,6 +17,8 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; @@ -247,15 +249,19 @@ void configJwtCookie() { ); } - @Test + @ParameterizedTest + @CsvSource({ + "ROLE_USER, false", + "ROLE_ADMIN, true" + }) @DisplayName("응답 정보에 멤버 관련 쿠키를 설정한다." + " (유저 닉네임은 UTF-8로 인코딩되고 이메일은 인코딩되지 않는다." + " secure은 true, httpOnly은 false로 저장한다.)") - void configMemberCookie() { + void configMemberCookie(String inputIsAdmin, String expectedIsAdmin) { // given MockHttpServletResponse response = new MockHttpServletResponse(); SocialMemberDto socialMemberDto = createSocialDto("dreamy5patisiel", "꿈빛파티시엘", - "꿈빛파티시엘", "1234", "email@gmail.com", SocialType.KAKAO.name(), Role.ROLE_USER.name()); + "꿈빛파티시엘", "1234", "email@gmail.com", SocialType.KAKAO.name(), inputIsAdmin); Member member = Member.createMemberBy(socialMemberDto); String encodedNickname = URLEncoder.encode(member.getNicknameAsString(), StandardCharsets.UTF_8); String email = member.getEmailAsString(); @@ -266,10 +272,12 @@ void configMemberCookie() { // then Cookie nicknameCookie = response.getCookie(JwtCookieConstant.DEVDEVDEV_MEMBER_NICKNAME); Cookie emailCookie = response.getCookie(JwtCookieConstant.DEVDEVDEV_MEMBER_EMAIL); + Cookie isAdmin = response.getCookie(JwtCookieConstant.DEVDEVDEV_MEMBER_IS_ADMIN); assertAll( () -> assertThat(nicknameCookie).isNotNull(), - () -> assertThat(emailCookie).isNotNull() + () -> assertThat(emailCookie).isNotNull(), + () -> assertThat(isAdmin).isNotNull() ); assertAll( @@ -287,6 +295,14 @@ void configMemberCookie() { () -> assertThat(emailCookie.getSecure()).isTrue(), () -> assertThat(emailCookie.isHttpOnly()).isFalse() ); + + assertAll( + () -> assertThat(isAdmin.getName()).isEqualTo(JwtCookieConstant.DEVDEVDEV_MEMBER_IS_ADMIN), + () -> assertThat(isAdmin.getValue()).isEqualTo(expectedIsAdmin), + () -> assertThat(isAdmin.getMaxAge()).isEqualTo(CookieUtils.DEFAULT_MAX_AGE), + () -> assertThat(isAdmin.getSecure()).isTrue(), + () -> assertThat(isAdmin.isHttpOnly()).isFalse() + ); } private SocialMemberDto createSocialDto(String userId, String name, String nickname, String password, String email, diff --git a/src/test/java/com/dreamypatisiel/devdevdev/web/controller/BlameControllerTest.java b/src/test/java/com/dreamypatisiel/devdevdev/web/controller/BlameControllerTest.java index 4af1d675..4a6c969f 100644 --- a/src/test/java/com/dreamypatisiel/devdevdev/web/controller/BlameControllerTest.java +++ b/src/test/java/com/dreamypatisiel/devdevdev/web/controller/BlameControllerTest.java @@ -112,7 +112,7 @@ void blamePick() throws Exception { memberRepository.save(member); // 픽픽픽 생성 - Pick pick = createPick("픽픽픽", ContentStatus.APPROVAL, new Count(0L)); + Pick pick = createPick(member, "픽픽픽", ContentStatus.APPROVAL, new Count(0L)); pickRepository.save(pick); // 신고 종류 생성 @@ -145,7 +145,7 @@ void blamePickComment() throws Exception { memberRepository.save(member); // 픽픽픽 생성 - Pick pick = createPick("픽픽픽", ContentStatus.APPROVAL, new Count(0L)); + Pick pick = createPick(member, "픽픽픽", ContentStatus.APPROVAL, new Count(0L)); pickRepository.save(pick); // 픽픽픽 댓글 생성 @@ -296,8 +296,9 @@ private PickComment createPickComment(Pick pick, Member createdBy, String conten return pickComment; } - private Pick createPick(String title, ContentStatus contentStatus, Count commentTotalCount) { + private Pick createPick(Member member, String title, ContentStatus contentStatus, Count commentTotalCount) { return Pick.builder() + .member(member) .title(new Title(title)) .contentStatus(contentStatus) .commentTotalCount(commentTotalCount) diff --git a/src/test/java/com/dreamypatisiel/devdevdev/web/controller/MyPageControllerTest.java b/src/test/java/com/dreamypatisiel/devdevdev/web/controller/MyPageControllerTest.java index 6101e4f5..9eab99c2 100644 --- a/src/test/java/com/dreamypatisiel/devdevdev/web/controller/MyPageControllerTest.java +++ b/src/test/java/com/dreamypatisiel/devdevdev/web/controller/MyPageControllerTest.java @@ -36,7 +36,7 @@ import com.dreamypatisiel.devdevdev.domain.entity.enums.PickOptionType; import com.dreamypatisiel.devdevdev.domain.entity.enums.Role; import com.dreamypatisiel.devdevdev.domain.entity.enums.SocialType; -import com.dreamypatisiel.devdevdev.domain.repository.BookmarkRepository; +import com.dreamypatisiel.devdevdev.domain.repository.techArticle.BookmarkRepository; import com.dreamypatisiel.devdevdev.domain.repository.CompanyRepository; import com.dreamypatisiel.devdevdev.domain.repository.member.MemberRepository; import com.dreamypatisiel.devdevdev.domain.repository.pick.PickOptionRepository; @@ -74,6 +74,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; @@ -91,6 +92,8 @@ class MyPageControllerTest extends SupportControllerTest { private static final int TEST_ARTICLES_COUNT = 20; private static List techArticles; + @Autowired + ApplicationContext applicationContext; @Autowired TechArticleRepository techArticleRepository; @Autowired diff --git a/src/test/java/com/dreamypatisiel/devdevdev/web/controller/MyPageControllerUsedMockServiceTest.java b/src/test/java/com/dreamypatisiel/devdevdev/web/controller/MyPageControllerUsedMockServiceTest.java new file mode 100644 index 00000000..71704320 --- /dev/null +++ b/src/test/java/com/dreamypatisiel/devdevdev/web/controller/MyPageControllerUsedMockServiceTest.java @@ -0,0 +1,226 @@ +package com.dreamypatisiel.devdevdev.web.controller; + +import static com.dreamypatisiel.devdevdev.web.dto.response.ResultType.SUCCESS; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +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.dreamypatisiel.devdevdev.domain.entity.Member; +import com.dreamypatisiel.devdevdev.domain.entity.enums.PickOptionType; +import com.dreamypatisiel.devdevdev.domain.entity.enums.Role; +import com.dreamypatisiel.devdevdev.domain.entity.enums.SocialType; +import com.dreamypatisiel.devdevdev.domain.repository.member.MemberRepository; +import com.dreamypatisiel.devdevdev.domain.service.member.MemberService; +import com.dreamypatisiel.devdevdev.global.constant.SecurityConstant; +import com.dreamypatisiel.devdevdev.global.security.oauth2.model.SocialMemberDto; +import com.dreamypatisiel.devdevdev.web.dto.SliceCustom; +import com.dreamypatisiel.devdevdev.web.dto.response.ResultType; +import com.dreamypatisiel.devdevdev.web.dto.response.comment.MyWrittenCommentResponse; +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.util.List; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.ResultActions; + +public class MyPageControllerUsedMockServiceTest extends SupportControllerTest { + + @Autowired + MemberRepository memberRepository; + @MockBean + MemberService memberService; + + @Test + @DisplayName("회원이 내가 썼어요 댓글을 조회한다.") + void getMyWrittenComments() throws Exception { + // given + SocialMemberDto socialMemberDto = createSocialDto("dreamy5patisiel", "꿈빛파티시엘", + "꿈빛파티시엘", "1234", email, socialType, role); + Member member = Member.createMemberBy(socialMemberDto); + memberRepository.save(member); + + Pageable pageable = PageRequest.of(0, 6); + LocalDateTime now = LocalDateTime.of(2025, 1, 1, 0, 0, 0); + + // 응답 생성 + MyWrittenCommentResponse pickWrittenComment = createMyWrittenCommentResponse( + "PICK_1_1", 1L, "픽픽픽 제목", 1L, "PICK", "픽픽픽 댓글입니다.", 111L, now, "픽픽픽 A", + PickOptionType.firstPickOption.name()); + MyWrittenCommentResponse techWrittenComment = createMyWrittenCommentResponse( + "TECH_1_1", 1L, "기술블로그 제목", 1L, "TECH", "기술블로그 댓글입니다.", 54321L, now, null, null); + List myWrittenComments = List.of(pickWrittenComment, techWrittenComment); + SliceCustom result = new SliceCustom<>(myWrittenComments, pageable, false, 2L); + + // when + when(memberService.findMyWrittenComments(eq(pageable), any(), any())).thenReturn(result); + + // then + mockMvc.perform(get("/devdevdev/api/v1/mypage/comments") + .queryParam("size", String.valueOf(pageable.getPageSize())) + .queryParam("pickCommentId", "1000") + .queryParam("techCommentId", "1000") + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding(StandardCharsets.UTF_8) + .header(SecurityConstant.AUTHORIZATION_HEADER, SecurityConstant.BEARER_PREFIX + accessToken)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.resultType").value(SUCCESS.name())) + .andExpect(jsonPath("$.data").isNotEmpty()) + .andExpect(jsonPath("$.data.content").isArray()) + .andExpect(jsonPath("$.data.content.[0].uniqueCommentId").isString()) + .andExpect(jsonPath("$.data.content.[0].postId").isNumber()) + .andExpect(jsonPath("$.data.content.[0].postTitle").isString()) + .andExpect(jsonPath("$.data.content.[0].commentId").isNumber()) + .andExpect(jsonPath("$.data.content.[0].commentType").isString()) + .andExpect(jsonPath("$.data.content.[0].commentContents").isString()) + .andExpect(jsonPath("$.data.content.[0].commentRecommendTotalCount").isNumber()) + .andExpect(jsonPath("$.data.content.[0].commentCreatedAt").isString()) + .andExpect(jsonPath("$.data.content.[0].pickOptionTitle").isString()) + .andExpect(jsonPath("$.data.content.[0].pickOptionType").isString()) + .andExpect(jsonPath("$.data.content.[1].uniqueCommentId").isString()) + .andExpect(jsonPath("$.data.content.[1].postId").isNumber()) + .andExpect(jsonPath("$.data.content.[1].postTitle").isString()) + .andExpect(jsonPath("$.data.content.[1].commentId").isNumber()) + .andExpect(jsonPath("$.data.content.[1].commentType").isString()) + .andExpect(jsonPath("$.data.content.[1].commentContents").isString()) + .andExpect(jsonPath("$.data.content.[1].commentCreatedAt").isString()) + .andExpect(jsonPath("$.data.content.[1].pickOptionTitle").isEmpty()) + .andExpect(jsonPath("$.data.content.[1].pickOptionType").isEmpty()) + .andExpect(jsonPath("$.data.pageable.pageNumber").isNumber()) + .andExpect(jsonPath("$.data.pageable.pageSize").isNumber()) + .andExpect(jsonPath("$.data.pageable.sort").isNotEmpty()) + .andExpect(jsonPath("$.data.pageable.sort.empty").isBoolean()) + .andExpect(jsonPath("$.data.pageable.sort.sorted").isBoolean()) + .andExpect(jsonPath("$.data.pageable.sort.unsorted").isBoolean()) + .andExpect(jsonPath("$.data.pageable.offset").isNumber()) + .andExpect(jsonPath("$.data.pageable.paged").isBoolean()) + .andExpect(jsonPath("$.data.pageable.unpaged").isBoolean()) + .andExpect(jsonPath("$.data.totalElements").value(2)) + .andExpect(jsonPath("$.data.first").isBoolean()) + .andExpect(jsonPath("$.data.last").isBoolean()) + .andExpect(jsonPath("$.data.size").isNumber()) + .andExpect(jsonPath("$.data.number").isNumber()) + .andExpect(jsonPath("$.data.sort").isNotEmpty()) + .andExpect(jsonPath("$.data.sort.empty").isBoolean()) + .andExpect(jsonPath("$.data.sort.sorted").isBoolean()) + .andExpect(jsonPath("$.data.sort.unsorted").isBoolean()) + .andExpect(jsonPath("$.data.numberOfElements").isNumber()) + .andExpect(jsonPath("$.data.empty").isBoolean()); + } + + @Test + @DisplayName("회원이 작성한 댓글을 조회할 때 회원이 유효하지 않으면 예외가 발생한다.") + void getMyWrittenCommentsMemberException() throws Exception { + // given + Pageable pageable = PageRequest.of(0, 6); + + // when // then + ResultActions actions = mockMvc.perform(get("/devdevdev/api/v1/mypage/comments") + .queryParam("size", String.valueOf(pageable.getPageSize())) + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding(StandardCharsets.UTF_8)) + .andDo(print()) + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.resultType").value(ResultType.FAIL.name())) + .andExpect(jsonPath("$.message").isString()) + .andExpect(jsonPath("$.errorCode").value(HttpStatus.UNAUTHORIZED.value())); + } + + @Disabled + @Test + @DisplayName("회원이 작성한 댓글을 조회할 때 기술블로그 댓글 아이디가 없으면 예외가 발생한다.") + void getMyWrittenCommentsTechCommentIdBindException() throws Exception { + // given + SocialMemberDto socialMemberDto = createSocialDto("dreamy5patisiel", "꿈빛파티시엘", + "꿈빛파티시엘", "1234", email, socialType, role); + Member member = Member.createMemberBy(socialMemberDto); + memberRepository.save(member); + + Pageable pageable = PageRequest.of(0, 6); + + // when // then + mockMvc.perform(get("/devdevdev/api/v1/mypage/comments") + .queryParam("size", String.valueOf(pageable.getPageSize())) + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding(StandardCharsets.UTF_8) + .header(SecurityConstant.AUTHORIZATION_HEADER, SecurityConstant.BEARER_PREFIX + accessToken)) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.resultType").value(ResultType.FAIL.name())) + .andExpect(jsonPath("$.message").isString()) + .andExpect(jsonPath("$.errorCode").value(HttpStatus.BAD_REQUEST.value())); + } + + @Disabled + @Test + @DisplayName("회원이 작성한 댓글을 조회할 때 픽픽픽 댓글 아이디가 없으면 예외가 발생한다.") + void getMyWrittenCommentsPickCommentIdBindException() throws Exception { + // given + SocialMemberDto socialMemberDto = createSocialDto("dreamy5patisiel", "꿈빛파티시엘", + "꿈빛파티시엘", "1234", email, socialType, role); + Member member = Member.createMemberBy(socialMemberDto); + memberRepository.save(member); + + Pageable pageable = PageRequest.of(0, 6); + + // when // then + mockMvc.perform(get("/devdevdev/api/v1/mypage/comments") + .queryParam("size", String.valueOf(pageable.getPageSize())) + .queryParam("techCommentId", "1000") + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding(StandardCharsets.UTF_8) + .header(SecurityConstant.AUTHORIZATION_HEADER, SecurityConstant.BEARER_PREFIX + accessToken)) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.resultType").value(ResultType.FAIL.name())) + .andExpect(jsonPath("$.message").isString()) + .andExpect(jsonPath("$.errorCode").value(HttpStatus.BAD_REQUEST.value())); + } + + private static MyWrittenCommentResponse createMyWrittenCommentResponse(String uniqueCommentId, Long postId, + String postTitle, + Long commentId, + String commentType, + String commentContents, + Long commentRecommendTotalCount, + LocalDateTime commentCreatedAt, + String pickOptionTitle, + String pickOptionType) { + return MyWrittenCommentResponse.builder() + .uniqueCommentId(uniqueCommentId) + .postId(postId) + .postTitle(postTitle) + .commentId(commentId) + .commentType(commentType) + .commentContents(commentContents) + .commentRecommendTotalCount(commentRecommendTotalCount) + .commentCreatedAt(commentCreatedAt) + .pickOptionTitle(pickOptionTitle) + .pickOptionType(pickOptionType) + .build(); + } + + private SocialMemberDto createSocialDto(String userId, String name, String nickName, String password, String email, + String socialType, String role) { + return SocialMemberDto.builder() + .userId(userId) + .name(name) + .nickname(nickName) + .password(password) + .email(email) + .socialType(SocialType.valueOf(socialType)) + .role(Role.valueOf(role)) + .build(); + } +} diff --git a/src/test/java/com/dreamypatisiel/devdevdev/web/controller/pick/PickControllerTest.java b/src/test/java/com/dreamypatisiel/devdevdev/web/controller/pick/PickControllerTest.java index 3b48b5ce..5e90aff1 100644 --- a/src/test/java/com/dreamypatisiel/devdevdev/web/controller/pick/PickControllerTest.java +++ b/src/test/java/com/dreamypatisiel/devdevdev/web/controller/pick/PickControllerTest.java @@ -105,24 +105,27 @@ class PickControllerTest extends SupportControllerTest { @DisplayName("회원이 픽픽픽 메인을 조회한다.") void getPicksMainByMember() throws Exception { // given + SocialMemberDto socialMemberDto = createSocialDto("dreamy5patisiel", "꿈빛파티시엘", + "꿈빛파티시엘", "1234", email, socialType, role); + Member member = Member.createMemberBy(socialMemberDto); + memberRepository.save(member); + PickOption pickOption1 = createPickOption(new Title("픽옵션1"), new PickOptionContents("픽콘텐츠1"), new Count(1), firstPickOption); PickOption pickOption2 = createPickOption(new Title("픽옵션2"), new PickOptionContents("픽콘텐츠2"), new Count(1), secondPickOption); + Title title = new Title("픽1타이틀"); Count count = new Count(2); String thumbnailUrl = "https://devdevdev.co.kr/devdevdev/api/v1/pick/image/1"; String author = "운영자"; - Pick pick = createPick(title, count, count, count, - thumbnailUrl, author, ContentStatus.APPROVAL, List.of(pickOption1, pickOption2), List.of()); + Pick pick = createPick(member, title, count, count, count, thumbnailUrl, author, ContentStatus.APPROVAL, + List.of(pickOption1, pickOption2), List.of()); pick.changePopularScore(pickPopularScorePolicy); pickRepository.save(pick); pickOptionRepository.saveAll(List.of(pickOption1, pickOption2)); - SocialMemberDto socialMemberDto = createSocialDto("dreamy5patisiel", "꿈빛파티시엘", - "꿈빛파티시엘", "1234", email, socialType, role); - Member member = Member.createMemberBy(socialMemberDto); member.updateRefreshToken(refreshToken); memberRepository.save(member); @@ -183,15 +186,21 @@ void getPicksMainByMember() throws Exception { @DisplayName("익명 사용자가 픽픽픽 메인을 조회한다.") void getPicksMainByAnonymous() throws Exception { // given + SocialMemberDto socialMemberDto = createSocialDto("dreamy5patisiel", "꿈빛파티시엘", + "꿈빛파티시엘", "1234", email, socialType, role); + Member member = Member.createMemberBy(socialMemberDto); + memberRepository.save(member); + PickOption pickOption1 = createPickOption(new Title("픽옵션1"), new PickOptionContents("픽콘텐츠1"), new Count(1), firstPickOption); PickOption pickOption2 = createPickOption(new Title("픽옵션2"), new PickOptionContents("픽콘텐츠2"), new Count(1), secondPickOption); + Title title = new Title("픽1타이틀"); Count count = new Count(2); String thumbnailUrl = "https://devdevdev.co.kr/devdevdev/api/v1/pick/image/1"; String author = "운영자"; - Pick pick = createPick(title, count, count, count, + Pick pick = createPick(member, title, count, count, count, thumbnailUrl, author, ContentStatus.APPROVAL, List.of(pickOption1, pickOption2), List.of()); pick.changePopularScore(pickPopularScorePolicy); @@ -1389,13 +1398,14 @@ private SocialMemberDto createSocialDto(String userId, String name, String nickN .build(); } - private Pick createPick(Title title, Count pickVoteTotalCount, Count pickViewTotalCount, + private Pick createPick(Member member, Title title, Count pickVoteTotalCount, Count pickViewTotalCount, Count pickcommentTotalCount, String thumbnailUrl, String author, ContentStatus contentStatus, List pickOptions, List pickVotes ) { Pick pick = Pick.builder() + .member(member) .title(title) .voteTotalCount(pickVoteTotalCount) .viewTotalCount(pickViewTotalCount) diff --git a/src/test/java/com/dreamypatisiel/devdevdev/web/controller/techArticle/TechArticleCommentControllerTest.java b/src/test/java/com/dreamypatisiel/devdevdev/web/controller/techArticle/TechArticleCommentControllerTest.java index 880f39b7..8f6d3feb 100644 --- a/src/test/java/com/dreamypatisiel/devdevdev/web/controller/techArticle/TechArticleCommentControllerTest.java +++ b/src/test/java/com/dreamypatisiel/devdevdev/web/controller/techArticle/TechArticleCommentControllerTest.java @@ -19,6 +19,7 @@ import com.dreamypatisiel.devdevdev.domain.entity.embedded.CommentContents; import com.dreamypatisiel.devdevdev.domain.entity.embedded.CompanyName; import com.dreamypatisiel.devdevdev.domain.entity.embedded.Count; +import com.dreamypatisiel.devdevdev.domain.entity.embedded.Title; import com.dreamypatisiel.devdevdev.domain.entity.embedded.Url; import com.dreamypatisiel.devdevdev.domain.entity.enums.Role; import com.dreamypatisiel.devdevdev.domain.entity.enums.SocialType; @@ -78,7 +79,8 @@ void registerTechCommentByAnonymous() throws Exception { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); @@ -106,7 +108,8 @@ void registerTechComment() throws Exception { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); @@ -142,7 +145,8 @@ void registerTechCommentNotFoundTechArticleException() throws Exception { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); @@ -177,7 +181,8 @@ void registerTechCommentNotFoundMemberException() throws Exception { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); @@ -207,7 +212,8 @@ void registerTechCommentContentsIsNullException(String contents) throws Exceptio Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); @@ -242,7 +248,8 @@ void modifyTechComment() throws Exception { member.updateRefreshToken(refreshToken); memberRepository.save(member); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -282,7 +289,8 @@ void modifyTechCommentContentsIsNullException(String contents) throws Exception member.updateRefreshToken(refreshToken); memberRepository.save(member); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -320,7 +328,8 @@ void modifyTechCommentNotFoundException() throws Exception { member.updateRefreshToken(refreshToken); memberRepository.save(member); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -353,7 +362,8 @@ void modifyTechCommentAlreadyDeletedException() throws Exception { member.updateRefreshToken(refreshToken); memberRepository.save(member); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -394,7 +404,8 @@ void deleteTechComment() throws Exception { member.updateRefreshToken(refreshToken); memberRepository.save(member); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -430,7 +441,8 @@ void deleteTechCommentNotFoundException() throws Exception { member.updateRefreshToken(refreshToken); memberRepository.save(member); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -457,7 +469,8 @@ void registerRepliedTechComment() throws Exception { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -510,7 +523,8 @@ void registerRepliedTechCommentContentsIsNullException(String contents) throws E Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); TechArticle savedTechArticle = techArticleRepository.save(techArticle); Long techArticleId = savedTechArticle.getId(); @@ -558,7 +572,8 @@ void getTechComments() throws Exception { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(12L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -674,7 +689,8 @@ void recommendTechComment() throws Exception { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); techArticleRepository.save(techArticle); @@ -722,7 +738,8 @@ void getTechBestComments() throws Exception { companyRepository.save(company); // 기술 블로그 생성 - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(12L), new Count(1L), null, company); techArticleRepository.save(techArticle); @@ -808,7 +825,8 @@ void getTechBestCommentsAnonymous() throws Exception { companyRepository.save(company); // 기술 블로그 생성 - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(12L), new Count(1L), null, company); techArticleRepository.save(techArticle); diff --git a/src/test/java/com/dreamypatisiel/devdevdev/web/controller/techArticle/TechArticleControllerTest.java b/src/test/java/com/dreamypatisiel/devdevdev/web/controller/techArticle/TechArticleControllerTest.java index a416b629..28947a86 100644 --- a/src/test/java/com/dreamypatisiel/devdevdev/web/controller/techArticle/TechArticleControllerTest.java +++ b/src/test/java/com/dreamypatisiel/devdevdev/web/controller/techArticle/TechArticleControllerTest.java @@ -18,12 +18,13 @@ import com.dreamypatisiel.devdevdev.domain.entity.TechArticle; import com.dreamypatisiel.devdevdev.domain.entity.embedded.CompanyName; import com.dreamypatisiel.devdevdev.domain.entity.embedded.Count; +import com.dreamypatisiel.devdevdev.domain.entity.embedded.Title; import com.dreamypatisiel.devdevdev.domain.entity.embedded.Url; import com.dreamypatisiel.devdevdev.domain.entity.enums.Role; import com.dreamypatisiel.devdevdev.domain.entity.enums.SocialType; -import com.dreamypatisiel.devdevdev.domain.repository.BookmarkRepository; import com.dreamypatisiel.devdevdev.domain.repository.CompanyRepository; import com.dreamypatisiel.devdevdev.domain.repository.member.MemberRepository; +import com.dreamypatisiel.devdevdev.domain.repository.techArticle.BookmarkRepository; import com.dreamypatisiel.devdevdev.domain.repository.techArticle.TechArticleRepository; import com.dreamypatisiel.devdevdev.domain.repository.techArticle.TechArticleSort; import com.dreamypatisiel.devdevdev.elastic.domain.document.ElasticTechArticle; @@ -320,10 +321,12 @@ void getTechArticlesSpecialSymbolException() throws Exception { void getTechArticleByAnonymous() throws Exception { // given Long id = firstTechArticle.getId(); + String anonymousMemberId = "GA1.1.276672604.1715872960"; // when // then mockMvc.perform(get("/devdevdev/api/v1/articles/{id}", id) .contentType(MediaType.APPLICATION_JSON) + .header("Anonymous-Member-Id", anonymousMemberId) .characterEncoding(StandardCharsets.UTF_8)) .andDo(print()) .andExpect(status().isOk()) @@ -344,7 +347,8 @@ void getTechArticleByAnonymous() throws Exception { .andExpect(jsonPath("$.data.viewTotalCount").isNumber()) .andExpect(jsonPath("$.data.recommendTotalCount").isNumber()) .andExpect(jsonPath("$.data.commentTotalCount").isNumber()) - .andExpect(jsonPath("$.data.popularScore").isNumber()); + .andExpect(jsonPath("$.data.popularScore").isNumber()) + .andExpect(jsonPath("$.data.isRecommended").isBoolean()); } @Test @@ -384,7 +388,8 @@ void getTechArticleByMember() throws Exception { .andExpect(jsonPath("$.data.recommendTotalCount").isNumber()) .andExpect(jsonPath("$.data.commentTotalCount").isNumber()) .andExpect(jsonPath("$.data.popularScore").isNumber()) - .andExpect(jsonPath("$.data.isBookmarked").isBoolean()); + .andExpect(jsonPath("$.data.isBookmarked").isBoolean()) + .andExpect(jsonPath("$.data.isRecommended").isBoolean()); } @Test @@ -409,7 +414,8 @@ void getTechArticleNotFoundMemberException() throws Exception { @DisplayName("기술블로그 상세를 조회할 때 기술블로그가 존재하지 않으면 예외가 발생한다.") void getTechArticleNotFoundTechArticleException() throws Exception { // given - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); @@ -431,7 +437,8 @@ void getTechArticleNotFoundTechArticleException() throws Exception { @DisplayName("기술블로그 상세를 조회할 때 엘라스틱ID가 존재하지 않으면 예외가 발생한다.") void getTechArticleNotFoundElasticIdException() throws Exception { // given - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); TechArticle savedTechArticle = techArticleRepository.save(techArticle); @@ -452,7 +459,8 @@ void getTechArticleNotFoundElasticIdException() throws Exception { @DisplayName("기술블로그 상세를 조회할 때 엘라스틱 기술블로그가 존재하지 않으면 예외가 발생한다.") void getTechArticleNotFoundElasticTechArticleException() throws Exception { // given - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), "elasticId", company); @@ -499,7 +507,8 @@ void updateBookmark() throws Exception { @DisplayName("회원이 기술블로그 북마크를 요청할 때 존재하지 않는 기술블로그라면 예외가 발생한다.") void updateBookmarkNotFoundTechArticleException() throws Exception { // given - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); @@ -542,6 +551,81 @@ void updateBookmarkNotFoundMemberException() throws Exception { .andExpect(jsonPath("$.errorCode").value(HttpStatus.NOT_FOUND.value())); } + @Test + @DisplayName("회원이 기술블로그 추천을 요청한다.") + void updateRecommend() throws Exception { + // given + Long id = firstTechArticle.getId(); + SocialMemberDto socialMemberDto = createSocialDto("dreamy5patisiel", "꿈빛파티시엘", + "꿈빛파티시엘", "1234", email, socialType, role); + Member member = Member.createMemberBy(socialMemberDto); + member.updateRefreshToken(refreshToken); + memberRepository.save(member); + + // when // then + mockMvc.perform(post("/devdevdev/api/v1/articles/{id}/recommend", id) + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding(StandardCharsets.UTF_8) + .header(SecurityConstant.AUTHORIZATION_HEADER, SecurityConstant.BEARER_PREFIX + accessToken)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.resultType").value(ResultType.SUCCESS.name())) + .andExpect(jsonPath("$.data").isNotEmpty()) + .andExpect(jsonPath("$.data").isMap()) + .andExpect(jsonPath("$.data.techArticleId").isNumber()) + .andExpect(jsonPath("$.data.status").isBoolean()) + .andExpect(jsonPath("$.data.recommendTotalCount").isNumber()); + + } + + @Test + @DisplayName("회원이 기술블로그 추천을 요청할 때 존재하지 않는 기술블로그라면 예외가 발생한다.") + void updateRecommendNotFoundTechArticleException() throws Exception { + // given + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), + new Count(1L), + new Count(1L), + new Count(1L), null, company); + TechArticle savedTechArticle = techArticleRepository.save(techArticle); + Long id = savedTechArticle.getId() + 1; + + SocialMemberDto socialMemberDto = createSocialDto("dreamy5patisiel", "꿈빛파티시엘", + "꿈빛파티시엘", "1234", email, socialType, role); + Member member = Member.createMemberBy(socialMemberDto); + member.updateRefreshToken(refreshToken); + memberRepository.save(member); + + // when // then + mockMvc.perform(post("/devdevdev/api/v1/articles/{id}/recommend", id) + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding(StandardCharsets.UTF_8) + .header(SecurityConstant.AUTHORIZATION_HEADER, SecurityConstant.BEARER_PREFIX + accessToken)) + .andDo(print()) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.resultType").value(ResultType.FAIL.name())) + .andExpect(jsonPath("$.message").value(NOT_FOUND_TECH_ARTICLE_MESSAGE)) + .andExpect(jsonPath("$.errorCode").value(HttpStatus.NOT_FOUND.value())); + } + + @Test + @DisplayName("회원이 기술블로그 추천을 요청할 때 존재하지 않는 회원이라면 예외가 발생한다.") + void updateRecommendNotFoundMemberException() throws Exception { + // given + Long id = firstTechArticle.getId(); + + // when // then + mockMvc.perform(post("/devdevdev/api/v1/articles/{id}/recommend", id) + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding(StandardCharsets.UTF_8) + .header(SecurityConstant.AUTHORIZATION_HEADER, SecurityConstant.BEARER_PREFIX + accessToken)) + .andDo(print()) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.resultType").value(ResultType.FAIL.name())) + .andExpect(jsonPath("$.message").value(INVALID_MEMBER_NOT_FOUND_MESSAGE)) + .andExpect(jsonPath("$.errorCode").value(HttpStatus.NOT_FOUND.value())); + } + private SocialMemberDto createSocialDto(String userId, String name, String nickName, String password, String email, String socialType, String role) { return SocialMemberDto.builder() diff --git a/src/test/java/com/dreamypatisiel/devdevdev/web/docs/BlameControllerDocsTest.java b/src/test/java/com/dreamypatisiel/devdevdev/web/docs/BlameControllerDocsTest.java index 723d9a09..b5933da9 100644 --- a/src/test/java/com/dreamypatisiel/devdevdev/web/docs/BlameControllerDocsTest.java +++ b/src/test/java/com/dreamypatisiel/devdevdev/web/docs/BlameControllerDocsTest.java @@ -150,7 +150,7 @@ void blamePick() throws Exception { memberRepository.save(member); // 픽픽픽 생성 - Pick pick = createPick("픽픽픽", ContentStatus.APPROVAL, new Count(0L)); + Pick pick = createPick(member, "픽픽픽", ContentStatus.APPROVAL, new Count(0L)); pickRepository.save(pick); // 신고 종류 생성 @@ -211,7 +211,7 @@ void blamePickComment() throws Exception { memberRepository.save(member); // 픽픽픽 생성 - Pick pick = createPick("픽픽픽", ContentStatus.APPROVAL, new Count(0L)); + Pick pick = createPick(member, "픽픽픽", ContentStatus.APPROVAL, new Count(0L)); pickRepository.save(pick); // 픽픽픽 댓글 생성 @@ -447,8 +447,9 @@ private PickComment createPickComment(Pick pick, Member createdBy, String conten return pickComment; } - private Pick createPick(String title, ContentStatus contentStatus, Count commentTotalCount) { + private Pick createPick(Member member, String title, ContentStatus contentStatus, Count commentTotalCount) { return Pick.builder() + .member(member) .title(new Title(title)) .contentStatus(contentStatus) .commentTotalCount(commentTotalCount) diff --git a/src/test/java/com/dreamypatisiel/devdevdev/web/docs/MyPageControllerDocsTest.java b/src/test/java/com/dreamypatisiel/devdevdev/web/docs/MyPageControllerDocsTest.java index dad44cde..2e8d4304 100644 --- a/src/test/java/com/dreamypatisiel/devdevdev/web/docs/MyPageControllerDocsTest.java +++ b/src/test/java/com/dreamypatisiel/devdevdev/web/docs/MyPageControllerDocsTest.java @@ -55,7 +55,7 @@ import com.dreamypatisiel.devdevdev.domain.entity.enums.PickOptionType; import com.dreamypatisiel.devdevdev.domain.entity.enums.Role; import com.dreamypatisiel.devdevdev.domain.entity.enums.SocialType; -import com.dreamypatisiel.devdevdev.domain.repository.BookmarkRepository; +import com.dreamypatisiel.devdevdev.domain.repository.techArticle.BookmarkRepository; import com.dreamypatisiel.devdevdev.domain.repository.CompanyRepository; import com.dreamypatisiel.devdevdev.domain.repository.member.MemberRepository; import com.dreamypatisiel.devdevdev.domain.repository.pick.PickOptionRepository; diff --git a/src/test/java/com/dreamypatisiel/devdevdev/web/docs/MyPageControllerDocsUsedMockServiceTest.java b/src/test/java/com/dreamypatisiel/devdevdev/web/docs/MyPageControllerDocsUsedMockServiceTest.java new file mode 100644 index 00000000..381ab133 --- /dev/null +++ b/src/test/java/com/dreamypatisiel/devdevdev/web/docs/MyPageControllerDocsUsedMockServiceTest.java @@ -0,0 +1,316 @@ +package com.dreamypatisiel.devdevdev.web.docs; + +import static com.dreamypatisiel.devdevdev.global.constant.SecurityConstant.AUTHORIZATION_HEADER; +import static com.dreamypatisiel.devdevdev.web.docs.format.ApiDocsFormatGenerator.authenticationType; +import static com.dreamypatisiel.devdevdev.web.docs.format.ApiDocsFormatGenerator.commentIdType; +import static com.dreamypatisiel.devdevdev.web.docs.format.ApiDocsFormatGenerator.myWrittenCommentSort; +import static com.dreamypatisiel.devdevdev.web.docs.format.ApiDocsFormatGenerator.stringOrNull; +import static com.dreamypatisiel.devdevdev.web.docs.format.ApiDocsFormatGenerator.uniqueCommentIdType; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; +import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; +import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.dreamypatisiel.devdevdev.domain.entity.Member; +import com.dreamypatisiel.devdevdev.domain.entity.enums.PickOptionType; +import com.dreamypatisiel.devdevdev.domain.entity.enums.Role; +import com.dreamypatisiel.devdevdev.domain.entity.enums.SocialType; +import com.dreamypatisiel.devdevdev.domain.repository.member.MemberRepository; +import com.dreamypatisiel.devdevdev.domain.service.member.MemberService; +import com.dreamypatisiel.devdevdev.global.constant.SecurityConstant; +import com.dreamypatisiel.devdevdev.global.security.oauth2.model.SocialMemberDto; +import com.dreamypatisiel.devdevdev.web.dto.SliceCustom; +import com.dreamypatisiel.devdevdev.web.dto.response.comment.MyWrittenCommentResponse; +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.util.List; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.http.MediaType; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.web.servlet.ResultActions; + +public class MyPageControllerDocsUsedMockServiceTest extends SupportControllerDocsTest { + + @Autowired + MemberRepository memberRepository; + @MockBean + MemberService memberService; + + @Test + @DisplayName("회원이 내가 썼어요 댓글을 조회한다.") + void getMyWrittenComments() throws Exception { + // given + SocialMemberDto socialMemberDto = createSocialDto("dreamy5patisiel", "꿈빛파티시엘", + "꿈빛파티시엘", "1234", email, socialType, role); + Member member = Member.createMemberBy(socialMemberDto); + memberRepository.save(member); + + Pageable pageable = PageRequest.of(0, 6); + LocalDateTime now = LocalDateTime.of(2025, 1, 1, 0, 0, 0); + + // 응답 생성 + MyWrittenCommentResponse pickWrittenComment = createMyWrittenCommentResponse( + "PICK_1_1", 1L, "픽픽픽 제목", 1L, "PICK", "픽픽픽 댓글입니다.", 111L, now, "픽픽픽 A", + PickOptionType.firstPickOption.name()); + MyWrittenCommentResponse techWrittenComment = createMyWrittenCommentResponse( + "TECH_1_1", 1L, "기술블로그 제목", 1L, "TECH", "기술블로그 댓글입니다.", 54321L, now, null, null); + List myWrittenComments = List.of(pickWrittenComment, techWrittenComment); + SliceCustom result = new SliceCustom<>(myWrittenComments, pageable, false, 2L); + + // when + when(memberService.findMyWrittenComments(eq(pageable), any(), any())).thenReturn(result); + + // then + ResultActions actions = mockMvc.perform(get("/devdevdev/api/v1/mypage/comments") + .queryParam("size", String.valueOf(pageable.getPageSize())) + .queryParam("pickCommentId", "1000") + .queryParam("techCommentId", "1000") + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding(StandardCharsets.UTF_8) + .header(SecurityConstant.AUTHORIZATION_HEADER, SecurityConstant.BEARER_PREFIX + accessToken)) + .andDo(print()) + .andExpect(status().isOk()); + + // docs + actions.andDo(document("mypage-comments", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestHeaders( + headerWithName(AUTHORIZATION_HEADER).description("Bearer 엑세스 토큰") + ), + queryParameters( + parameterWithName("size").optional().description("조회되는 데이터 수"), + parameterWithName("pickCommentId").optional().description("가장 작은 픽픽픽 아이디"), + parameterWithName("techCommentId").optional().description("가장 작은 기술블로그 아이디"), + parameterWithName("commentFilter").optional().description("댓글 정렬 기준") + .attributes(myWrittenCommentSort()) + ), + responseFields( + fieldWithPath("resultType").type(JsonFieldType.STRING).description("응답 결과"), + fieldWithPath("data").type(JsonFieldType.OBJECT).description("응답 데이터"), + + fieldWithPath("data.content").type(JsonFieldType.ARRAY).description("기술블로그 메인 배열"), + fieldWithPath("data.content.[].uniqueCommentId").type(JsonFieldType.STRING) + .description("댓글 유니크 아이디").attributes(uniqueCommentIdType(), authenticationType()), + fieldWithPath("data.content.[].postId").type(JsonFieldType.NUMBER) + .description("픽픽픽 | 기술블로그 아이디").attributes(authenticationType()), + fieldWithPath("data.content.[].postTitle").type(JsonFieldType.STRING) + .description("픽픽픽 | 기술블로그 제목").attributes(authenticationType()), + fieldWithPath("data.content.[].commentId").type(JsonFieldType.NUMBER) + .description("픽픽픽 | 기술블로그 댓글 아이디").attributes(authenticationType()), + fieldWithPath("data.content.[].commentType").type(JsonFieldType.STRING) + .description("픽픽픽 | 기술블로그 댓글 타입").attributes(commentIdType(), authenticationType()), + fieldWithPath("data.content.[].commentContents").type(JsonFieldType.STRING) + .description("픽픽픽 | 기술블로그 댓글 내용").attributes(authenticationType()), + fieldWithPath("data.content.[].commentRecommendTotalCount").type(JsonFieldType.NUMBER) + .description("픽픽픽 | 기술블로그 댓글 추천 갯수").attributes(authenticationType()), + fieldWithPath("data.content.[].commentCreatedAt").type(JsonFieldType.STRING) + .description("픽픽픽 | 기술블로그 댓글 생성일").attributes(authenticationType()), + fieldWithPath("data.content.[].pickOptionTitle").optional().type(JsonFieldType.STRING) + .description("픽픽픽 옵션 제목").attributes(stringOrNull(), authenticationType()), + fieldWithPath("data.content.[].pickOptionType").optional().type(JsonFieldType.STRING) + .description("픽픽픽 옵션 타입").attributes(stringOrNull(), authenticationType()), + + fieldWithPath("data.pageable").type(JsonFieldType.OBJECT).description("페이지네이션 정보") + .attributes(authenticationType()), + fieldWithPath("data.pageable.pageNumber").type(JsonFieldType.NUMBER).description("페이지 번호") + .attributes(authenticationType()), + fieldWithPath("data.pageable.pageSize").type(JsonFieldType.NUMBER).description("페이지 사이즈") + .attributes(authenticationType()), + + fieldWithPath("data.pageable.sort").type(JsonFieldType.OBJECT).description("정렬 정보") + .attributes(authenticationType()), + fieldWithPath("data.pageable.sort.empty").type(JsonFieldType.BOOLEAN) + .description("정렬 정보가 비어있는지 여부").attributes(authenticationType()), + fieldWithPath("data.pageable.sort.sorted").type(JsonFieldType.BOOLEAN).description("정렬 여부") + .attributes(authenticationType()), + fieldWithPath("data.pageable.sort.unsorted").type(JsonFieldType.BOOLEAN).description("비정렬 여부") + .attributes(authenticationType()), + + fieldWithPath("data.pageable.offset").type(JsonFieldType.NUMBER) + .description("페이지 오프셋 (페이지 크기 * 페이지 번호)").attributes(authenticationType()), + fieldWithPath("data.pageable.paged").type(JsonFieldType.BOOLEAN).description("페이지 정보 포함 여부") + .attributes(authenticationType()), + fieldWithPath("data.pageable.unpaged").type(JsonFieldType.BOOLEAN).description("페이지 정보 비포함 여부") + .attributes(authenticationType()), + + fieldWithPath("data.totalElements").type(JsonFieldType.NUMBER).description("데이터 전체 갯수") + .attributes(authenticationType()), + fieldWithPath("data.first").type(JsonFieldType.BOOLEAN).description("현재 페이지가 첫 페이지 여부") + .attributes(authenticationType()), + fieldWithPath("data.last").type(JsonFieldType.BOOLEAN).description("현재 페이지가 마지막 페이지 여부") + .attributes(authenticationType()), + fieldWithPath("data.size").type(JsonFieldType.NUMBER).description("페이지 크기") + .attributes(authenticationType()), + fieldWithPath("data.number").type(JsonFieldType.NUMBER).description("현재 페이지") + .attributes(authenticationType()), + + fieldWithPath("data.sort").type(JsonFieldType.OBJECT).description("정렬 정보") + .attributes(authenticationType()), + fieldWithPath("data.sort.empty").type(JsonFieldType.BOOLEAN).description("정렬 정보가 비어있는지 여부") + .attributes(authenticationType()), + fieldWithPath("data.sort.sorted").type(JsonFieldType.BOOLEAN).description("정렬 상태 여부") + .attributes(authenticationType()), + fieldWithPath("data.sort.unsorted").type(JsonFieldType.BOOLEAN).description("비정렬 상태 여부") + .attributes(authenticationType()), + fieldWithPath("data.numberOfElements").type(JsonFieldType.NUMBER).description("현재 페이지 데이터 수") + .attributes(authenticationType()), + fieldWithPath("data.empty").type(JsonFieldType.BOOLEAN).description("현재 빈 페이지 여부") + .attributes(authenticationType()) + ) + )); + } + + @Test + @DisplayName("회원이 작성한 댓글을 조회할 때 회원이 유효하지 않으면 예외가 발생한다.") + void getMyWrittenCommentsMemberException() throws Exception { + // given + Pageable pageable = PageRequest.of(0, 6); + + // when // then + ResultActions actions = mockMvc.perform(get("/devdevdev/api/v1/mypage/comments") + .queryParam("size", String.valueOf(pageable.getPageSize())) + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding(StandardCharsets.UTF_8)) + .andDo(print()) + .andExpect(status().isUnauthorized()); + + // docs + actions.andDo(document("mypage-comments-member-exception", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + responseFields( + fieldWithPath("resultType").type(JsonFieldType.STRING).description("응답 결과"), + fieldWithPath("message").type(JsonFieldType.STRING).description("에러 메시지"), + fieldWithPath("errorCode").type(JsonFieldType.NUMBER).description("에러 코드") + ) + )); + } + + @Disabled + @Test + @DisplayName("회원이 작성한 댓글을 조회할 때 기술블로그 댓글 아이디가 없으면 예외가 발생한다.") + void getMyWrittenCommentsTechCommentIdBindException() throws Exception { + // given + SocialMemberDto socialMemberDto = createSocialDto("dreamy5patisiel", "꿈빛파티시엘", + "꿈빛파티시엘", "1234", email, socialType, role); + Member member = Member.createMemberBy(socialMemberDto); + memberRepository.save(member); + + Pageable pageable = PageRequest.of(0, 6); + + // when // then + ResultActions actions = mockMvc.perform(get("/devdevdev/api/v1/mypage/comments") + .queryParam("size", String.valueOf(pageable.getPageSize())) + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding(StandardCharsets.UTF_8) + .header(AUTHORIZATION_HEADER, SecurityConstant.BEARER_PREFIX + accessToken)) + .andDo(print()) + .andExpect(status().isBadRequest()); + + // docs + actions.andDo(document("mypage-comments-tech-comment-id-bind-exception", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestHeaders( + headerWithName(AUTHORIZATION_HEADER).description("Bearer 엑세스 토큰") + ), + responseFields( + fieldWithPath("resultType").type(JsonFieldType.STRING).description("응답 결과"), + fieldWithPath("message").type(JsonFieldType.STRING).description("에러 메시지"), + fieldWithPath("errorCode").type(JsonFieldType.NUMBER).description("에러 코드") + ) + )); + } + + @Disabled + @Test + @DisplayName("회원이 작성한 댓글을 조회할 때 픽픽픽 댓글 아이디가 없으면 예외가 발생한다.") + void getMyWrittenCommentsPickCommentIdBindException() throws Exception { + // given + SocialMemberDto socialMemberDto = createSocialDto("dreamy5patisiel", "꿈빛파티시엘", + "꿈빛파티시엘", "1234", email, socialType, role); + Member member = Member.createMemberBy(socialMemberDto); + memberRepository.save(member); + + Pageable pageable = PageRequest.of(0, 6); + + // when // then + ResultActions actions = mockMvc.perform(get("/devdevdev/api/v1/mypage/comments") + .queryParam("size", String.valueOf(pageable.getPageSize())) + .queryParam("techCommentId", "1000") + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding(StandardCharsets.UTF_8) + .header(AUTHORIZATION_HEADER, SecurityConstant.BEARER_PREFIX + accessToken)) + .andDo(print()) + .andExpect(status().isBadRequest()); + + // docs + actions.andDo(document("mypage-comments-pick-comment-id-bind-exception", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestHeaders( + headerWithName(AUTHORIZATION_HEADER).description("Bearer 엑세스 토큰") + ), + responseFields( + fieldWithPath("resultType").type(JsonFieldType.STRING).description("응답 결과"), + fieldWithPath("message").type(JsonFieldType.STRING).description("에러 메시지"), + fieldWithPath("errorCode").type(JsonFieldType.NUMBER).description("에러 코드") + ) + )); + } + + private static MyWrittenCommentResponse createMyWrittenCommentResponse(String uniqueCommentId, Long postId, + String postTitle, + Long commentId, + String commentType, + String commentContents, + Long commentRecommendTotalCount, + LocalDateTime commentCreatedAt, + String pickOptionTitle, + String pickOptionType) { + return MyWrittenCommentResponse.builder() + .uniqueCommentId(uniqueCommentId) + .postId(postId) + .postTitle(postTitle) + .commentId(commentId) + .commentType(commentType) + .commentContents(commentContents) + .commentRecommendTotalCount(commentRecommendTotalCount) + .commentCreatedAt(commentCreatedAt) + .pickOptionTitle(pickOptionTitle) + .pickOptionType(pickOptionType) + .build(); + } + + private SocialMemberDto createSocialDto(String userId, String name, String nickName, String password, String email, + String socialType, String role) { + return SocialMemberDto.builder() + .userId(userId) + .name(name) + .nickname(nickName) + .password(password) + .email(email) + .socialType(SocialType.valueOf(socialType)) + .role(Role.valueOf(role)) + .build(); + } +} diff --git a/src/test/java/com/dreamypatisiel/devdevdev/web/docs/PickControllerDocsTest.java b/src/test/java/com/dreamypatisiel/devdevdev/web/docs/PickControllerDocsTest.java index a8343922..466c9bb1 100644 --- a/src/test/java/com/dreamypatisiel/devdevdev/web/docs/PickControllerDocsTest.java +++ b/src/test/java/com/dreamypatisiel/devdevdev/web/docs/PickControllerDocsTest.java @@ -102,6 +102,7 @@ public class PickControllerDocsTest extends SupportControllerDocsTest { + @Autowired PickRepository pickRepository; @Autowired diff --git a/src/test/java/com/dreamypatisiel/devdevdev/web/docs/TechArticleCommentControllerDocsTest.java b/src/test/java/com/dreamypatisiel/devdevdev/web/docs/TechArticleCommentControllerDocsTest.java index 6018bab3..2bd0ea02 100644 --- a/src/test/java/com/dreamypatisiel/devdevdev/web/docs/TechArticleCommentControllerDocsTest.java +++ b/src/test/java/com/dreamypatisiel/devdevdev/web/docs/TechArticleCommentControllerDocsTest.java @@ -38,6 +38,7 @@ import com.dreamypatisiel.devdevdev.domain.entity.embedded.CommentContents; import com.dreamypatisiel.devdevdev.domain.entity.embedded.CompanyName; import com.dreamypatisiel.devdevdev.domain.entity.embedded.Count; +import com.dreamypatisiel.devdevdev.domain.entity.embedded.Title; import com.dreamypatisiel.devdevdev.domain.entity.embedded.Url; import com.dreamypatisiel.devdevdev.domain.entity.enums.Role; import com.dreamypatisiel.devdevdev.domain.entity.enums.SocialType; @@ -100,7 +101,8 @@ void registerTechCommentByAnonymous() throws Exception { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long id = techArticle.getId(); @@ -141,7 +143,8 @@ void registerTechComment() throws Exception { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); @@ -198,7 +201,8 @@ void registerTechCommentNotFoundTechArticleException() throws Exception { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); @@ -248,7 +252,8 @@ void registerTechCommentNotFoundMemberException() throws Exception { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); @@ -294,8 +299,8 @@ void registerTechCommentContentsIsNullException(String contents) throws Exceptio Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), - new Count(1L), new Count(1L), new Count(1L), null, company); + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); TechArticle savedTechArticle = techArticleRepository.save(techArticle); Long techArticleId = savedTechArticle.getId(); @@ -342,7 +347,8 @@ void modifyTechComment() throws Exception { member.updateRefreshToken(refreshToken); memberRepository.save(member); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -404,7 +410,8 @@ void modifyTechCommentContentsIsNullException(String contents) throws Exception member.updateRefreshToken(refreshToken); memberRepository.save(member); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -458,7 +465,8 @@ void modifyTechCommentNotFoundException() throws Exception { member.updateRefreshToken(refreshToken); memberRepository.save(member); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -507,7 +515,8 @@ void deleteTechComment() throws Exception { member.updateRefreshToken(refreshToken); memberRepository.save(member); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -562,7 +571,8 @@ void deleteTechCommentNotFoundException() throws Exception { member.updateRefreshToken(refreshToken); memberRepository.save(member); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -605,7 +615,8 @@ void registerTechReply() throws Exception { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -680,7 +691,8 @@ void registerTechReplyContentsIsNullException(String contents) throws Exception Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); TechArticle savedTechArticle = techArticleRepository.save(techArticle); Long techArticleId = savedTechArticle.getId(); @@ -744,7 +756,8 @@ void getTechComments() throws Exception { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(12L), new Count(1L), null, company); techArticleRepository.save(techArticle); Long techArticleId = techArticle.getId(); @@ -970,7 +983,8 @@ void recommendTechComment() throws Exception { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); techArticleRepository.save(techArticle); @@ -1026,7 +1040,8 @@ void recommendTechCommentNotFoundTechComment() throws Exception { Company company = createCompany("꿈빛 파티시엘", "https://example.png", "https://example.com", "https://example.com"); companyRepository.save(company); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); techArticleRepository.save(techArticle); @@ -1084,7 +1099,8 @@ void getTechBestComments() throws Exception { companyRepository.save(company); // 기술 블로그 생성 - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(12L), new Count(1L), null, company); techArticleRepository.save(techArticle); diff --git a/src/test/java/com/dreamypatisiel/devdevdev/web/docs/TechArticleControllerDocsTest.java b/src/test/java/com/dreamypatisiel/devdevdev/web/docs/TechArticleControllerDocsTest.java index 07fd3d46..eb6a0ad1 100644 --- a/src/test/java/com/dreamypatisiel/devdevdev/web/docs/TechArticleControllerDocsTest.java +++ b/src/test/java/com/dreamypatisiel/devdevdev/web/docs/TechArticleControllerDocsTest.java @@ -28,10 +28,11 @@ import com.dreamypatisiel.devdevdev.domain.entity.TechArticle; import com.dreamypatisiel.devdevdev.domain.entity.embedded.CompanyName; import com.dreamypatisiel.devdevdev.domain.entity.embedded.Count; +import com.dreamypatisiel.devdevdev.domain.entity.embedded.Title; import com.dreamypatisiel.devdevdev.domain.entity.embedded.Url; import com.dreamypatisiel.devdevdev.domain.entity.enums.Role; import com.dreamypatisiel.devdevdev.domain.entity.enums.SocialType; -import com.dreamypatisiel.devdevdev.domain.repository.BookmarkRepository; +import com.dreamypatisiel.devdevdev.domain.repository.techArticle.BookmarkRepository; import com.dreamypatisiel.devdevdev.domain.repository.CompanyRepository; import com.dreamypatisiel.devdevdev.domain.repository.member.MemberRepository; import com.dreamypatisiel.devdevdev.domain.repository.techArticle.TechArticleRepository; @@ -353,7 +354,8 @@ void getTechArticleByMember() throws Exception { preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), requestHeaders( - headerWithName(AUTHORIZATION_HEADER).optional().description("Bearer 엑세스 토큰") + headerWithName(AUTHORIZATION_HEADER).optional().description("Bearer 엑세스 토큰"), + headerWithName("Anonymous-Member-Id").optional().description("익명 회원 아이디") ), pathParameters( parameterWithName("techArticleId").description("기술블로그 아이디") @@ -382,7 +384,9 @@ void getTechArticleByMember() throws Exception { fieldWithPath("data.commentTotalCount").type(JsonFieldType.NUMBER).description("기술블로그 댓글수"), fieldWithPath("data.popularScore").type(JsonFieldType.NUMBER).description("기술블로그 인기점수"), fieldWithPath("data.isBookmarked").attributes(authenticationType()).type(JsonFieldType.BOOLEAN) - .description("회원의 북마크 여부(익명 사용자는 필드가 없다)") + .description("회원의 북마크 여부(익명 사용자는 필드가 없다)"), + fieldWithPath("data.isRecommended").attributes(authenticationType()).type(JsonFieldType.BOOLEAN) + .description("회원의 추천 여부") ) )); } @@ -424,13 +428,20 @@ void getTechArticleNotFoundMemberException() throws Exception { @DisplayName("기술블로그 상세를 조회할 때 기술블로그가 존재하지 않으면 예외가 발생한다.") void getTechArticleNotFoundTechArticleException() throws Exception { // given - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); TechArticle savedTechArticle = techArticleRepository.save(techArticle); Long id = savedTechArticle.getId() + 1; + SocialMemberDto socialMemberDto = createSocialDto("dreamy5patisiel", "꿈빛파티시엘", + "꿈빛파티시엘", "1234", email, socialType, role); + Member member = Member.createMemberBy(socialMemberDto); + member.updateRefreshToken(refreshToken); + memberRepository.save(member); + // when // then ResultActions actions = mockMvc.perform(get("/devdevdev/api/v1/articles/{techArticleId}", id) .contentType(MediaType.APPLICATION_JSON) @@ -453,7 +464,8 @@ void getTechArticleNotFoundTechArticleException() throws Exception { @DisplayName("기술블로그 상세를 조회할 때 엘라스틱ID가 존재하지 않으면 예외가 발생한다.") void getTechArticleNotFoundElasticIdException() throws Exception { // given - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); @@ -482,7 +494,8 @@ void getTechArticleNotFoundElasticIdException() throws Exception { @DisplayName("기술블로그 상세를 조회할 때 엘라스틱 기술블로그가 존재하지 않으면 예외가 발생한다.") void getTechArticleNotFoundElasticTechArticleException() throws Exception { // given - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), "elasticId", company); @@ -545,6 +558,46 @@ void updateBookmark() throws Exception { )); } + @Test + @DisplayName("회원이 기술블로그 추천을 요청한다.") + void updateRecommend() throws Exception { + // given + Long id = firstTechArticle.getId(); + SocialMemberDto socialMemberDto = createSocialDto("dreamy5patisiel", "꿈빛파티시엘", + "꿈빛파티시엘", "1234", email, socialType, role); + Member member = Member.createMemberBy(socialMemberDto); + member.updateRefreshToken(refreshToken); + memberRepository.save(member); + + // when // then + ResultActions actions = mockMvc.perform(post("/devdevdev/api/v1/articles/{techArticleId}/recommend", id) + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding(StandardCharsets.UTF_8) + .header(SecurityConstant.AUTHORIZATION_HEADER, SecurityConstant.BEARER_PREFIX + accessToken)) + .andDo(print()); + + // Docs + actions.andDo(document("tech-article-recommend", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestHeaders( + headerWithName(AUTHORIZATION_HEADER).optional().description("Bearer 엑세스 토큰"), + headerWithName("Anonymous-Member-Id").optional().description("익명 회원 아이디") + ), + pathParameters( + parameterWithName("techArticleId").description("기술블로그 아이디") + ), + responseFields( + fieldWithPath("resultType").type(JsonFieldType.STRING).description("응답 결과"), + fieldWithPath("data").type(JsonFieldType.OBJECT).description("응답 데이터"), + + fieldWithPath("data.techArticleId").type(JsonFieldType.NUMBER).description("기술블로그 아이디"), + fieldWithPath("data.status").type(JsonFieldType.BOOLEAN).description("추천 상태"), + fieldWithPath("data.recommendTotalCount").type(JsonFieldType.NUMBER).description("기술블로그 총 추천수") + ) + )); + } + private SocialMemberDto createSocialDto(String userId, String name, String nickName, String password, String email, String socialType, String role) { return SocialMemberDto.builder() diff --git a/src/test/java/com/dreamypatisiel/devdevdev/web/docs/format/ApiDocsFormatGenerator.java b/src/test/java/com/dreamypatisiel/devdevdev/web/docs/format/ApiDocsFormatGenerator.java index 73234195..d5563235 100644 --- a/src/test/java/com/dreamypatisiel/devdevdev/web/docs/format/ApiDocsFormatGenerator.java +++ b/src/test/java/com/dreamypatisiel/devdevdev/web/docs/format/ApiDocsFormatGenerator.java @@ -10,6 +10,7 @@ import com.dreamypatisiel.devdevdev.domain.repository.techArticle.TechArticleSort; import com.dreamypatisiel.devdevdev.domain.repository.techArticle.TechCommentSort; import com.dreamypatisiel.devdevdev.domain.service.pick.MemberPickService; +import com.dreamypatisiel.devdevdev.web.dto.request.comment.MyWrittenCommentFilter; import com.dreamypatisiel.devdevdev.web.dto.request.common.BlamePathType; import java.util.Arrays; import java.util.stream.Collectors; @@ -104,4 +105,20 @@ static Attributes.Attribute blamePathType() { return key(FORMAT).value(blamePathType); } + + static Attributes.Attribute uniqueCommentIdType() { + return key(FORMAT).value("${commentType}_${postId}_${commentId}"); + } + + static Attributes.Attribute commentIdType() { + return key(FORMAT).value("PICK | TECH"); + } + + static Attributes.Attribute myWrittenCommentSort() { + String blamePathType = Arrays.stream(MyWrittenCommentFilter.values()) + .map(Enum::name) + .collect(Collectors.joining(COMMA)); + + return key(FORMAT).value(blamePathType); + } } diff --git a/src/test/java/com/dreamypatisiel/devdevdev/web/dto/response/techArticle/TechArticleMainResponseTest.java b/src/test/java/com/dreamypatisiel/devdevdev/web/dto/response/techArticle/TechArticleMainResponseTest.java index f4b2cd19..4652fd79 100644 --- a/src/test/java/com/dreamypatisiel/devdevdev/web/dto/response/techArticle/TechArticleMainResponseTest.java +++ b/src/test/java/com/dreamypatisiel/devdevdev/web/dto/response/techArticle/TechArticleMainResponseTest.java @@ -1,18 +1,20 @@ package com.dreamypatisiel.devdevdev.web.dto.response.techArticle; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + import com.dreamypatisiel.devdevdev.domain.entity.Company; import com.dreamypatisiel.devdevdev.domain.entity.TechArticle; import com.dreamypatisiel.devdevdev.domain.entity.embedded.CompanyName; import com.dreamypatisiel.devdevdev.domain.entity.embedded.Count; +import com.dreamypatisiel.devdevdev.domain.entity.embedded.Title; import com.dreamypatisiel.devdevdev.domain.entity.embedded.Url; import com.dreamypatisiel.devdevdev.elastic.domain.document.ElasticTechArticle; +import java.time.LocalDate; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import java.time.LocalDate; - -import static org.junit.jupiter.api.Assertions.*; - class TechArticleMainResponseTest { @Test @@ -22,7 +24,8 @@ public void setThumbnailImageWhenPresent() { Company company = createCompany("꿈빛 파티시엘", "https://officialImageUrl.png", "https://officialUrl.com", "https://careerUrl.com"); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); ElasticTechArticle elasticTechArticle = createElasticTechArticle("elasticId", "타이틀", LocalDate.now(), @@ -47,7 +50,8 @@ public void setLogoImageWhenThumbnailIsAbsent() { Company company = createCompany("꿈빛 파티시엘", "https://officialImageUrl.png", "https://officialUrl.com", "https://careerUrl.com"); - TechArticle techArticle = TechArticle.createTechArticle(new Url("https://example.com"), new Count(1L), + TechArticle techArticle = TechArticle.createTechArticle(new Title("기술블로그 제목"), new Url("https://example.com"), + new Count(1L), new Count(1L), new Count(1L), new Count(1L), null, company); ElasticTechArticle elasticTechArticle = createElasticTechArticle("elasticId", "타이틀", LocalDate.now(), diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml index c0e64474..812b9456 100644 --- a/src/test/resources/application-test.yml +++ b/src/test/resources/application-test.yml @@ -46,8 +46,16 @@ spring: max-idle: 8 min-idle: 2 +#MyBatis +mybatis: + type-aliases-package: com.dreamypatisiel.devdevdev.domain.repository + configuration: + map-underscore-to-camel-case: true + mapper-locations: classpath:mapper/**/*.xml + logging: level: org.hibernate.SQL: DEBUG org.hibernate.type: TRACE - org.springframework.jdbc: DEBUG \ No newline at end of file + org.springframework.jdbc: DEBUG + com.dreamypatisiel.devdevdev: TRACE \ No newline at end of file