diff --git a/.github/workflows/maeilmail-main-ci.yml b/.github/workflows/maeilmail-main-ci.yml index aa9b496..f5c4404 100644 --- a/.github/workflows/maeilmail-main-ci.yml +++ b/.github/workflows/maeilmail-main-ci.yml @@ -15,10 +15,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Github Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: JDK 17 구성 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '17' distribution: 'corretto' @@ -42,8 +42,13 @@ jobs: - name: 테스트 결과 출력 uses: EnricoMi/publish-unit-test-result-action@v2 + if: always() with: - files: | - ./maeil-mail/build/test-results/**/*.xml - ./maeil-wiki/build/test-results/**/*.xml - ./maeil-support/build/test-results/**/*.xml + files: '**/build/test-results/**/*.xml' + + - name: 테스트 실패 시 코멘트 생성 + uses: mikepenz/action-junit-report@v5 + if: failure() + with: + report_paths: '**/build/test-results/test/TEST-*.xml' + fail_on_failure: true diff --git a/.gitignore b/.gitignore index 15fb2f4..fe498b9 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,4 @@ out/ ### Database ### **/db test-db/ +.run/ diff --git a/maeil-mail/src/main/java/maeilmail/MaeilMailApplication.java b/maeil-mail/src/main/java/maeilmail/MaeilMailApplication.java index 2d92f5d..81babea 100644 --- a/maeil-mail/src/main/java/maeilmail/MaeilMailApplication.java +++ b/maeil-mail/src/main/java/maeilmail/MaeilMailApplication.java @@ -2,9 +2,11 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; import org.springframework.scheduling.annotation.EnableScheduling; @EnableScheduling +@ConfigurationPropertiesScan(basePackages = "maeilwiki") @SpringBootApplication(scanBasePackages = {"maeilmail", "maeilwiki", "maeilsupport"}) public class MaeilMailApplication { diff --git a/maeil-mail/src/main/java/maeilmail/support/JpaConfig.java b/maeil-mail/src/main/java/maeilmail/support/JpaConfig.java new file mode 100644 index 0000000..7dcdbc2 --- /dev/null +++ b/maeil-mail/src/main/java/maeilmail/support/JpaConfig.java @@ -0,0 +1,11 @@ +package maeilmail.support; + +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; + +@Configuration +@EntityScan(basePackages = {"maeilmail", "maeilwiki"}) +@EnableJpaRepositories(basePackages = {"maeilmail", "maeilwiki"}) +public class JpaConfig { +} diff --git a/maeil-mail/src/main/java/maeilmail/support/JpaAuditingConfig.java b/maeil-support/src/main/java/maeilsupport/config/JpaAuditingConfig.java similarity index 87% rename from maeil-mail/src/main/java/maeilmail/support/JpaAuditingConfig.java rename to maeil-support/src/main/java/maeilsupport/config/JpaAuditingConfig.java index fcb8ec2..ba8eac3 100644 --- a/maeil-mail/src/main/java/maeilmail/support/JpaAuditingConfig.java +++ b/maeil-support/src/main/java/maeilsupport/config/JpaAuditingConfig.java @@ -1,4 +1,4 @@ -package maeilmail.support; +package maeilsupport.config; import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; diff --git a/maeil-mail/src/main/java/maeilmail/support/QueryDslConfig.java b/maeil-support/src/main/java/maeilsupport/config/QueryDslConfig.java similarity index 94% rename from maeil-mail/src/main/java/maeilmail/support/QueryDslConfig.java rename to maeil-support/src/main/java/maeilsupport/config/QueryDslConfig.java index 5d454c7..72e94db 100644 --- a/maeil-mail/src/main/java/maeilmail/support/QueryDslConfig.java +++ b/maeil-support/src/main/java/maeilsupport/config/QueryDslConfig.java @@ -1,4 +1,4 @@ -package maeilmail.support; +package maeilsupport.config; import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.persistence.EntityManager; diff --git a/maeil-wiki/src/main/java/maeilwiki/WikiConfiguration.java b/maeil-wiki/src/main/java/maeilwiki/WikiConfiguration.java deleted file mode 100644 index aa0071c..0000000 --- a/maeil-wiki/src/main/java/maeilwiki/WikiConfiguration.java +++ /dev/null @@ -1,13 +0,0 @@ -package maeilwiki; - -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.context.properties.ConfigurationPropertiesScan; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; - -@Configuration -@ComponentScan -@EnableAutoConfiguration -@ConfigurationPropertiesScan -public class WikiConfiguration { -} diff --git a/maeil-wiki/src/main/java/maeilwiki/comment/CommentRequest.java b/maeil-wiki/src/main/java/maeilwiki/comment/application/CommentRequest.java similarity index 62% rename from maeil-wiki/src/main/java/maeilwiki/comment/CommentRequest.java rename to maeil-wiki/src/main/java/maeilwiki/comment/application/CommentRequest.java index 7cd6ca6..b1d219b 100644 --- a/maeil-wiki/src/main/java/maeilwiki/comment/CommentRequest.java +++ b/maeil-wiki/src/main/java/maeilwiki/comment/application/CommentRequest.java @@ -1,6 +1,7 @@ -package maeilwiki.comment; +package maeilwiki.comment.application; -import maeilwiki.member.Member; +import maeilwiki.comment.domain.Comment; +import maeilwiki.member.domain.Member; public record CommentRequest(String answer, boolean isAnonymous) { diff --git a/maeil-wiki/src/main/java/maeilwiki/comment/application/CommentResponse.java b/maeil-wiki/src/main/java/maeilwiki/comment/application/CommentResponse.java new file mode 100644 index 0000000..45990b9 --- /dev/null +++ b/maeil-wiki/src/main/java/maeilwiki/comment/application/CommentResponse.java @@ -0,0 +1,22 @@ +package maeilwiki.comment.application; + +import java.time.LocalDateTime; +import maeilwiki.comment.dto.CommentSummary; +import maeilwiki.member.dto.MemberThumbnail; + +public record CommentResponse( + Long id, + String answer, + LocalDateTime createdAt, + MemberThumbnail owner +) { + + public static CommentResponse from(CommentSummary commentSummary) { + return new CommentResponse( + commentSummary.id(), + commentSummary.answer(), + commentSummary.createdAt(), + commentSummary.owner() + ); + } +} diff --git a/maeil-wiki/src/main/java/maeilwiki/comment/CommentService.java b/maeil-wiki/src/main/java/maeilwiki/comment/application/CommentService.java similarity index 86% rename from maeil-wiki/src/main/java/maeilwiki/comment/CommentService.java rename to maeil-wiki/src/main/java/maeilwiki/comment/application/CommentService.java index 83b5f56..9c96e56 100644 --- a/maeil-wiki/src/main/java/maeilwiki/comment/CommentService.java +++ b/maeil-wiki/src/main/java/maeilwiki/comment/application/CommentService.java @@ -1,10 +1,14 @@ -package maeilwiki.comment; +package maeilwiki.comment.application; import java.util.NoSuchElementException; import lombok.RequiredArgsConstructor; +import maeilwiki.comment.domain.Comment; +import maeilwiki.comment.domain.CommentLike; +import maeilwiki.comment.domain.CommentLikeRepository; +import maeilwiki.comment.domain.CommentRepository; import maeilwiki.member.Identity; -import maeilwiki.member.Member; -import maeilwiki.member.MemberService; +import maeilwiki.member.application.MemberService; +import maeilwiki.member.domain.Member; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; diff --git a/maeil-wiki/src/main/java/maeilwiki/comment/Comment.java b/maeil-wiki/src/main/java/maeilwiki/comment/domain/Comment.java similarity index 95% rename from maeil-wiki/src/main/java/maeilwiki/comment/Comment.java rename to maeil-wiki/src/main/java/maeilwiki/comment/domain/Comment.java index ddce60d..73e9bc6 100644 --- a/maeil-wiki/src/main/java/maeilwiki/comment/Comment.java +++ b/maeil-wiki/src/main/java/maeilwiki/comment/domain/Comment.java @@ -1,4 +1,4 @@ -package maeilwiki.comment; +package maeilwiki.comment.domain; import java.time.LocalDateTime; import jakarta.persistence.Column; @@ -13,7 +13,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; import maeilsupport.BaseEntity; -import maeilwiki.member.Member; +import maeilwiki.member.domain.Member; @Entity @Getter diff --git a/maeil-wiki/src/main/java/maeilwiki/comment/CommentLike.java b/maeil-wiki/src/main/java/maeilwiki/comment/domain/CommentLike.java similarity index 94% rename from maeil-wiki/src/main/java/maeilwiki/comment/CommentLike.java rename to maeil-wiki/src/main/java/maeilwiki/comment/domain/CommentLike.java index 4632630..530032e 100644 --- a/maeil-wiki/src/main/java/maeilwiki/comment/CommentLike.java +++ b/maeil-wiki/src/main/java/maeilwiki/comment/domain/CommentLike.java @@ -1,4 +1,4 @@ -package maeilwiki.comment; +package maeilwiki.comment.domain; import java.time.LocalDateTime; import jakarta.persistence.Column; @@ -13,7 +13,7 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; -import maeilwiki.member.Member; +import maeilwiki.member.domain.Member; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; diff --git a/maeil-wiki/src/main/java/maeilwiki/comment/CommentLikeRepository.java b/maeil-wiki/src/main/java/maeilwiki/comment/domain/CommentLikeRepository.java similarity index 60% rename from maeil-wiki/src/main/java/maeilwiki/comment/CommentLikeRepository.java rename to maeil-wiki/src/main/java/maeilwiki/comment/domain/CommentLikeRepository.java index f325a3f..7113a25 100644 --- a/maeil-wiki/src/main/java/maeilwiki/comment/CommentLikeRepository.java +++ b/maeil-wiki/src/main/java/maeilwiki/comment/domain/CommentLikeRepository.java @@ -1,9 +1,9 @@ -package maeilwiki.comment; +package maeilwiki.comment.domain; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; -interface CommentLikeRepository extends JpaRepository { +public interface CommentLikeRepository extends JpaRepository { Optional findByCommentIdAndMemberId(Long commentId, Long memberId); } diff --git a/maeil-wiki/src/main/java/maeilwiki/comment/CommentRepository.java b/maeil-wiki/src/main/java/maeilwiki/comment/domain/CommentRepository.java similarity index 73% rename from maeil-wiki/src/main/java/maeilwiki/comment/CommentRepository.java rename to maeil-wiki/src/main/java/maeilwiki/comment/domain/CommentRepository.java index 83f3f32..ed4a757 100644 --- a/maeil-wiki/src/main/java/maeilwiki/comment/CommentRepository.java +++ b/maeil-wiki/src/main/java/maeilwiki/comment/domain/CommentRepository.java @@ -1,8 +1,8 @@ -package maeilwiki.comment; +package maeilwiki.comment.domain; import org.springframework.data.jpa.repository.JpaRepository; -public interface CommentRepository extends JpaRepository { +public interface CommentRepository extends JpaRepository, CommentRepositoryCustom { boolean existsByWikiIdAndDeletedAtIsNull(Long wikiId); } diff --git a/maeil-wiki/src/main/java/maeilwiki/comment/domain/CommentRepositoryCustom.java b/maeil-wiki/src/main/java/maeilwiki/comment/domain/CommentRepositoryCustom.java new file mode 100644 index 0000000..381df4f --- /dev/null +++ b/maeil-wiki/src/main/java/maeilwiki/comment/domain/CommentRepositoryCustom.java @@ -0,0 +1,9 @@ +package maeilwiki.comment.domain; + +import java.util.List; +import maeilwiki.comment.dto.CommentSummary; + +public interface CommentRepositoryCustom { + + List queryAllByWikiId(Long wikiId); +} diff --git a/maeil-wiki/src/main/java/maeilwiki/comment/domain/CommentRepositoryImpl.java b/maeil-wiki/src/main/java/maeilwiki/comment/domain/CommentRepositoryImpl.java new file mode 100644 index 0000000..d593eec --- /dev/null +++ b/maeil-wiki/src/main/java/maeilwiki/comment/domain/CommentRepositoryImpl.java @@ -0,0 +1,44 @@ +package maeilwiki.comment.domain; + +import static maeilwiki.comment.domain.QComment.comment; +import static maeilwiki.member.domain.QMember.member; + +import java.util.List; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import maeilwiki.comment.dto.CommentSummary; +import maeilwiki.comment.dto.QCommentSummary; +import maeilwiki.member.dto.QMemberThumbnail; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Transactional(readOnly = true) +class CommentRepositoryImpl implements CommentRepositoryCustom { + + private final JPAQueryFactory queryFactory; + + @Override + public List queryAllByWikiId(Long wikiId) { + return queryFactory.select(projectionCommentSummary()) + .from(comment) + .join(member).on(comment.member.eq(member)) + .where(comment.wikiId.eq(wikiId) + .and(comment.deletedAt.isNull())) + .orderBy(comment.id.asc()) + .fetch(); + } + + private QCommentSummary projectionCommentSummary() { + return new QCommentSummary( + comment.id, + comment.answer, + comment.isAnonymous, + comment.createdAt, + projectionMemberThumbnail() + ); + } + + private QMemberThumbnail projectionMemberThumbnail() { + return new QMemberThumbnail(member.name, member.profileImageUrl, member.githubUrl); + } +} diff --git a/maeil-wiki/src/main/java/maeilwiki/comment/dto/CommentSummary.java b/maeil-wiki/src/main/java/maeilwiki/comment/dto/CommentSummary.java new file mode 100644 index 0000000..12791f7 --- /dev/null +++ b/maeil-wiki/src/main/java/maeilwiki/comment/dto/CommentSummary.java @@ -0,0 +1,22 @@ +package maeilwiki.comment.dto; + +import java.time.LocalDateTime; +import com.querydsl.core.annotations.QueryProjection; +import maeilwiki.member.dto.MemberThumbnail; + +public record CommentSummary( + Long id, + String answer, + boolean isAnonymous, + LocalDateTime createdAt, + MemberThumbnail owner +) { + + @QueryProjection + public CommentSummary { + } + + public CommentSummary toAnonymousOwner() { + return new CommentSummary(id, answer, isAnonymous, createdAt, null); + } +} diff --git a/maeil-wiki/src/main/java/maeilwiki/member/MemberRequest.java b/maeil-wiki/src/main/java/maeilwiki/member/MemberRequest.java deleted file mode 100644 index 9e5eccb..0000000 --- a/maeil-wiki/src/main/java/maeilwiki/member/MemberRequest.java +++ /dev/null @@ -1,4 +0,0 @@ -package maeilwiki.member; - -record MemberRequest(String accessToken, String clientSecret) { -} diff --git a/maeil-wiki/src/main/java/maeilwiki/member/MemberTokenGenerator.java b/maeil-wiki/src/main/java/maeilwiki/member/MemberTokenGenerator.java index e838c7a..b6e267e 100644 --- a/maeil-wiki/src/main/java/maeilwiki/member/MemberTokenGenerator.java +++ b/maeil-wiki/src/main/java/maeilwiki/member/MemberTokenGenerator.java @@ -4,11 +4,12 @@ import java.util.Date; import io.jsonwebtoken.Jwts; import lombok.RequiredArgsConstructor; +import maeilwiki.member.domain.Member; import org.springframework.stereotype.Component; @Component @RequiredArgsConstructor -class MemberTokenGenerator { +public class MemberTokenGenerator { private final MemberTokenProperties properties; diff --git a/maeil-wiki/src/main/java/maeilwiki/member/MemberTokenResponse.java b/maeil-wiki/src/main/java/maeilwiki/member/MemberTokenResponse.java deleted file mode 100644 index c6d5c3a..0000000 --- a/maeil-wiki/src/main/java/maeilwiki/member/MemberTokenResponse.java +++ /dev/null @@ -1,4 +0,0 @@ -package maeilwiki.member; - -record MemberTokenResponse(String accessToken, String refreshToken) { -} diff --git a/maeil-wiki/src/main/java/maeilwiki/member/MemberApi.java b/maeil-wiki/src/main/java/maeilwiki/member/api/MemberApi.java similarity index 76% rename from maeil-wiki/src/main/java/maeilwiki/member/MemberApi.java rename to maeil-wiki/src/main/java/maeilwiki/member/api/MemberApi.java index 2390370..51179f4 100644 --- a/maeil-wiki/src/main/java/maeilwiki/member/MemberApi.java +++ b/maeil-wiki/src/main/java/maeilwiki/member/api/MemberApi.java @@ -1,6 +1,9 @@ -package maeilwiki.member; +package maeilwiki.member.api; import lombok.RequiredArgsConstructor; +import maeilwiki.member.application.MemberRequest; +import maeilwiki.member.application.MemberService; +import maeilwiki.member.application.MemberTokenResponse; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; diff --git a/maeil-wiki/src/main/java/maeilwiki/member/application/MemberRequest.java b/maeil-wiki/src/main/java/maeilwiki/member/application/MemberRequest.java new file mode 100644 index 0000000..6e1c69b --- /dev/null +++ b/maeil-wiki/src/main/java/maeilwiki/member/application/MemberRequest.java @@ -0,0 +1,4 @@ +package maeilwiki.member.application; + +public record MemberRequest(String accessToken, String clientSecret) { +} diff --git a/maeil-wiki/src/main/java/maeilwiki/member/MemberService.java b/maeil-wiki/src/main/java/maeilwiki/member/application/MemberService.java similarity index 93% rename from maeil-wiki/src/main/java/maeilwiki/member/MemberService.java rename to maeil-wiki/src/main/java/maeilwiki/member/application/MemberService.java index fb2dbf9..2ab8d3e 100644 --- a/maeil-wiki/src/main/java/maeilwiki/member/MemberService.java +++ b/maeil-wiki/src/main/java/maeilwiki/member/application/MemberService.java @@ -1,8 +1,11 @@ -package maeilwiki.member; +package maeilwiki.member.application; import java.util.NoSuchElementException; import java.util.function.Supplier; import lombok.RequiredArgsConstructor; +import maeilwiki.member.MemberTokenGenerator; +import maeilwiki.member.domain.Member; +import maeilwiki.member.domain.MemberRepository; import maeilwiki.member.github.GithubMemberFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; diff --git a/maeil-wiki/src/main/java/maeilwiki/member/application/MemberTokenResponse.java b/maeil-wiki/src/main/java/maeilwiki/member/application/MemberTokenResponse.java new file mode 100644 index 0000000..7101097 --- /dev/null +++ b/maeil-wiki/src/main/java/maeilwiki/member/application/MemberTokenResponse.java @@ -0,0 +1,4 @@ +package maeilwiki.member.application; + +public record MemberTokenResponse(String accessToken, String refreshToken) { +} diff --git a/maeil-wiki/src/main/java/maeilwiki/member/Member.java b/maeil-wiki/src/main/java/maeilwiki/member/domain/Member.java similarity index 93% rename from maeil-wiki/src/main/java/maeilwiki/member/Member.java rename to maeil-wiki/src/main/java/maeilwiki/member/domain/Member.java index b56a363..c1c4ba0 100644 --- a/maeil-wiki/src/main/java/maeilwiki/member/Member.java +++ b/maeil-wiki/src/main/java/maeilwiki/member/domain/Member.java @@ -1,4 +1,4 @@ -package maeilwiki.member; +package maeilwiki.member.domain; import java.time.LocalDateTime; import jakarta.persistence.Column; @@ -25,7 +25,7 @@ public class Member extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(nullable = false, unique = true, length = MAX_NAME_LENGTH) + @Column(nullable = false, length = MAX_NAME_LENGTH) private String name; @Column(nullable = false, unique = true) @@ -35,6 +35,9 @@ public class Member extends BaseEntity { @Enumerated(value = EnumType.STRING) private Provider provider; + @Column(nullable = true) + private String githubUrl; + @Setter @Column(nullable = false) private String refreshToken; diff --git a/maeil-wiki/src/main/java/maeilwiki/member/MemberRepository.java b/maeil-wiki/src/main/java/maeilwiki/member/domain/MemberRepository.java similarity index 87% rename from maeil-wiki/src/main/java/maeilwiki/member/MemberRepository.java rename to maeil-wiki/src/main/java/maeilwiki/member/domain/MemberRepository.java index e69af9d..86962f2 100644 --- a/maeil-wiki/src/main/java/maeilwiki/member/MemberRepository.java +++ b/maeil-wiki/src/main/java/maeilwiki/member/domain/MemberRepository.java @@ -1,4 +1,4 @@ -package maeilwiki.member; +package maeilwiki.member.domain; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/maeil-wiki/src/main/java/maeilwiki/member/Provider.java b/maeil-wiki/src/main/java/maeilwiki/member/domain/Provider.java similarity index 94% rename from maeil-wiki/src/main/java/maeilwiki/member/Provider.java rename to maeil-wiki/src/main/java/maeilwiki/member/domain/Provider.java index 41e16e1..568aa06 100644 --- a/maeil-wiki/src/main/java/maeilwiki/member/Provider.java +++ b/maeil-wiki/src/main/java/maeilwiki/member/domain/Provider.java @@ -1,4 +1,4 @@ -package maeilwiki.member; +package maeilwiki.member.domain; import java.util.Arrays; import java.util.NoSuchElementException; diff --git a/maeil-wiki/src/main/java/maeilwiki/member/dto/MemberThumbnail.java b/maeil-wiki/src/main/java/maeilwiki/member/dto/MemberThumbnail.java new file mode 100644 index 0000000..a577881 --- /dev/null +++ b/maeil-wiki/src/main/java/maeilwiki/member/dto/MemberThumbnail.java @@ -0,0 +1,14 @@ +package maeilwiki.member.dto; + +import com.querydsl.core.annotations.QueryProjection; + +public record MemberThumbnail( + String name, + String profileImage, + String github +) { + + @QueryProjection + public MemberThumbnail { + } +} diff --git a/maeil-wiki/src/main/java/maeilwiki/member/github/GithubMember.java b/maeil-wiki/src/main/java/maeilwiki/member/github/GithubMember.java index 8e08fd7..1f06075 100644 --- a/maeil-wiki/src/main/java/maeilwiki/member/github/GithubMember.java +++ b/maeil-wiki/src/main/java/maeilwiki/member/github/GithubMember.java @@ -1,7 +1,7 @@ package maeilwiki.member.github; -import maeilwiki.member.Member; -import maeilwiki.member.Provider; +import maeilwiki.member.domain.Member; +import maeilwiki.member.domain.Provider; public record GithubMember( Long id, diff --git a/maeil-wiki/src/main/java/maeilwiki/member/github/GithubMemberFactory.java b/maeil-wiki/src/main/java/maeilwiki/member/github/GithubMemberFactory.java index ce92cfa..c9b8048 100644 --- a/maeil-wiki/src/main/java/maeilwiki/member/github/GithubMemberFactory.java +++ b/maeil-wiki/src/main/java/maeilwiki/member/github/GithubMemberFactory.java @@ -1,7 +1,7 @@ package maeilwiki.member.github; import lombok.RequiredArgsConstructor; -import maeilwiki.member.Member; +import maeilwiki.member.domain.Member; import org.springframework.stereotype.Component; @Component diff --git a/maeil-wiki/src/main/java/maeilwiki/wiki/WikiService.java b/maeil-wiki/src/main/java/maeilwiki/wiki/WikiService.java deleted file mode 100644 index 2740b80..0000000 --- a/maeil-wiki/src/main/java/maeilwiki/wiki/WikiService.java +++ /dev/null @@ -1,63 +0,0 @@ -package maeilwiki.wiki; - -import java.util.NoSuchElementException; -import lombok.RequiredArgsConstructor; -import maeilwiki.comment.CommentRepository; -import maeilwiki.comment.CommentRequest; -import maeilwiki.comment.CommentService; -import maeilwiki.member.Identity; -import maeilwiki.member.Member; -import maeilwiki.member.MemberService; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@RequiredArgsConstructor -class WikiService { - - private final WikiRepository wikiRepository; - private final CommentRepository commentRepository; - private final CommentService commentService; - private final MemberService memberService; - - @Transactional - public void create(Identity identity, WikiRequest request) { - Member member = memberService.findById(identity.id()); - Wiki wiki = request.toWiki(member); - - wikiRepository.save(wiki); - } - - @Transactional - public void remove(Identity identity, Long wikiId) { - Wiki wiki = wikiRepository.findById(wikiId) - .orElseThrow(NoSuchElementException::new); - validateOwner(identity, wiki); - validateHasComment(wikiId); - - wiki.remove(); - } - - private void validateOwner(Identity identity, Wiki wiki) { - Member owner = wiki.getMember(); - - if (!identity.canAccessToResource(owner.getId())) { - throw new IllegalStateException("자신의 위키만 삭제할 수 있습니다."); - } - } - - private void validateHasComment(Long wikiId) { - boolean hasComment = commentRepository.existsByWikiIdAndDeletedAtIsNull(wikiId); - if (hasComment) { - throw new IllegalStateException("답변이 존재하는 위키는 삭제할 수 없습니다."); - } - } - - @Transactional - public void comment(Identity identity, CommentRequest request, Long wikiId) { - Wiki wiki = wikiRepository.findByIdAndDeletedAtIsNull(wikiId) - .orElseThrow(NoSuchElementException::new); - - commentService.comment(identity, request, wiki.getId()); - } -} diff --git a/maeil-wiki/src/main/java/maeilwiki/wiki/WikiApi.java b/maeil-wiki/src/main/java/maeilwiki/wiki/api/WikiApi.java similarity index 67% rename from maeil-wiki/src/main/java/maeilwiki/wiki/WikiApi.java rename to maeil-wiki/src/main/java/maeilwiki/wiki/api/WikiApi.java index 618f319..d9eb55b 100644 --- a/maeil-wiki/src/main/java/maeilwiki/wiki/WikiApi.java +++ b/maeil-wiki/src/main/java/maeilwiki/wiki/api/WikiApi.java @@ -1,14 +1,22 @@ -package maeilwiki.wiki; +package maeilwiki.wiki.api; import lombok.RequiredArgsConstructor; -import maeilwiki.comment.CommentRequest; -import maeilwiki.comment.CommentService; +import maeilsupport.PaginationResponse; +import maeilwiki.comment.application.CommentRequest; +import maeilwiki.comment.application.CommentService; import maeilwiki.member.Identity; +import maeilwiki.wiki.application.WikiRequest; +import maeilwiki.wiki.application.WikiResponse; +import maeilwiki.wiki.application.WikiService; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; +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.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @@ -69,4 +77,21 @@ public ResponseEntity toggleLike( return ResponseEntity.noContent().build(); } + + @GetMapping("/wiki/{id}") + public ResponseEntity getWiki(@PathVariable Long id) { + WikiResponse wiki = wikiService.getWikiById(id); + + return ResponseEntity.ok(wiki); + } + + @GetMapping("/wiki") + public ResponseEntity> getWikis( + @RequestParam(defaultValue = "all") String category, + @PageableDefault Pageable pageable + ) { + PaginationResponse response = wikiService.pageByCategory(category, pageable); + + return ResponseEntity.ok(response); + } } diff --git a/maeil-wiki/src/main/java/maeilwiki/wiki/WikiRequest.java b/maeil-wiki/src/main/java/maeilwiki/wiki/application/WikiRequest.java similarity index 56% rename from maeil-wiki/src/main/java/maeilwiki/wiki/WikiRequest.java rename to maeil-wiki/src/main/java/maeilwiki/wiki/application/WikiRequest.java index a026e9e..71bed67 100644 --- a/maeil-wiki/src/main/java/maeilwiki/wiki/WikiRequest.java +++ b/maeil-wiki/src/main/java/maeilwiki/wiki/application/WikiRequest.java @@ -1,8 +1,9 @@ -package maeilwiki.wiki; +package maeilwiki.wiki.application; -import maeilwiki.member.Member; +import maeilwiki.member.domain.Member; +import maeilwiki.wiki.domain.Wiki; -record WikiRequest(String question, String questionDetail, String category, boolean isAnonymous) { +public record WikiRequest(String question, String questionDetail, String category, boolean isAnonymous) { public Wiki toWiki(Member member) { if (questionDetail == null || questionDetail.isBlank()) { diff --git a/maeil-wiki/src/main/java/maeilwiki/wiki/application/WikiResponse.java b/maeil-wiki/src/main/java/maeilwiki/wiki/application/WikiResponse.java new file mode 100644 index 0000000..169a29b --- /dev/null +++ b/maeil-wiki/src/main/java/maeilwiki/wiki/application/WikiResponse.java @@ -0,0 +1,48 @@ +package maeilwiki.wiki.application; + +import static com.fasterxml.jackson.annotation.JsonInclude.Include; + +import java.time.LocalDateTime; +import java.util.List; +import com.fasterxml.jackson.annotation.JsonInclude; +import maeilwiki.comment.application.CommentResponse; +import maeilwiki.member.dto.MemberThumbnail; +import maeilwiki.wiki.dto.WikiSummary; + +public record WikiResponse( + Long id, + String question, + @JsonInclude(Include.NON_NULL) String questionDetail, + String category, + MemberThumbnail owner, + LocalDateTime createdAt, + @JsonInclude(Include.NON_NULL) List comments, + @JsonInclude(Include.NON_NULL) Long commentCount +) { + + public static WikiResponse withComments(WikiSummary wikiSummary, List commentResponses) { + return new WikiResponse( + wikiSummary.id(), + wikiSummary.question(), + wikiSummary.questionDetail(), + wikiSummary.category(), + wikiSummary.owner(), + wikiSummary.createdAt(), + commentResponses, + null + ); + } + + public static WikiResponse withCommentCount(WikiSummary wikiSummary, Long commentCount) { + return new WikiResponse( + wikiSummary.id(), + wikiSummary.question(), + wikiSummary.questionDetail(), + wikiSummary.category(), + wikiSummary.owner(), + wikiSummary.createdAt(), + null, + commentCount + ); + } +} diff --git a/maeil-wiki/src/main/java/maeilwiki/wiki/application/WikiService.java b/maeil-wiki/src/main/java/maeilwiki/wiki/application/WikiService.java new file mode 100644 index 0000000..2c22301 --- /dev/null +++ b/maeil-wiki/src/main/java/maeilwiki/wiki/application/WikiService.java @@ -0,0 +1,111 @@ +package maeilwiki.wiki.application; + +import java.util.List; +import java.util.NoSuchElementException; +import lombok.RequiredArgsConstructor; +import maeilsupport.PaginationResponse; +import maeilwiki.comment.application.CommentRequest; +import maeilwiki.comment.application.CommentResponse; +import maeilwiki.comment.application.CommentService; +import maeilwiki.comment.domain.CommentRepository; +import maeilwiki.comment.dto.CommentSummary; +import maeilwiki.member.Identity; +import maeilwiki.member.application.MemberService; +import maeilwiki.member.domain.Member; +import maeilwiki.wiki.domain.Wiki; +import maeilwiki.wiki.domain.WikiRepository; +import maeilwiki.wiki.dto.WikiSummary; +import maeilwiki.wiki.dto.WikiSummaryWithCommentCount; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class WikiService { + + private final WikiRepository wikiRepository; + private final CommentRepository commentRepository; + private final CommentService commentService; + private final MemberService memberService; + + @Transactional + public void create(Identity identity, WikiRequest request) { + Member member = memberService.findById(identity.id()); + Wiki wiki = request.toWiki(member); + + wikiRepository.save(wiki); + } + + @Transactional + public void remove(Identity identity, Long wikiId) { + Wiki wiki = wikiRepository.findById(wikiId) + .orElseThrow(NoSuchElementException::new); + validateOwner(identity, wiki); + validateHasComment(wikiId); + + wiki.remove(); + } + + private void validateOwner(Identity identity, Wiki wiki) { + Member owner = wiki.getMember(); + + if (!identity.canAccessToResource(owner.getId())) { + throw new IllegalStateException("자신의 위키만 삭제할 수 있습니다."); + } + } + + private void validateHasComment(Long wikiId) { + boolean hasComment = commentRepository.existsByWikiIdAndDeletedAtIsNull(wikiId); + if (hasComment) { + throw new IllegalStateException("답변이 존재하는 위키는 삭제할 수 없습니다."); + } + } + + @Transactional + public void comment(Identity identity, CommentRequest request, Long wikiId) { + Wiki wiki = wikiRepository.findByIdAndDeletedAtIsNull(wikiId) + .orElseThrow(NoSuchElementException::new); + + commentService.comment(identity, request, wiki.getId()); + } + + @Transactional(readOnly = true) + public WikiResponse getWikiById(Long wikiId) { + WikiSummary wikiSummary = resolveAnonymousWiki(wikiRepository.queryOneById(wikiId) + .orElseThrow(() -> new NoSuchElementException("존재하지 않는 위키입니다."))); + List commentResponses = commentRepository.queryAllByWikiId(wikiId) + .stream() + .map(this::resolveAnonymousComment) + .map(CommentResponse::from) + .toList(); + + return WikiResponse.withComments(wikiSummary, commentResponses); + } + + @Transactional(readOnly = true) + public PaginationResponse pageByCategory(String category, Pageable pageable) { + Page pageResults = wikiRepository.pageByCategory(category, pageable); + List wikiResponses = pageResults.getContent() + .stream() + .map(it -> WikiResponse.withCommentCount(resolveAnonymousWiki(it.wikiSummary()), it.commentCount())) + .toList(); + + return new PaginationResponse<>(pageResults.isLast(), (long) pageResults.getTotalPages(), wikiResponses); + } + + private WikiSummary resolveAnonymousWiki(WikiSummary wikiSummary) { + if (wikiSummary.isAnonymous()) { + return wikiSummary.toAnonymousOwner(); + } + return wikiSummary; + } + + private CommentSummary resolveAnonymousComment(CommentSummary commentSummary) { + if (commentSummary.isAnonymous()) { + return commentSummary.toAnonymousOwner(); + } + return commentSummary; + } +} diff --git a/maeil-wiki/src/main/java/maeilwiki/wiki/Wiki.java b/maeil-wiki/src/main/java/maeilwiki/wiki/domain/Wiki.java similarity index 97% rename from maeil-wiki/src/main/java/maeilwiki/wiki/Wiki.java rename to maeil-wiki/src/main/java/maeilwiki/wiki/domain/Wiki.java index 9c46a73..c153bc6 100644 --- a/maeil-wiki/src/main/java/maeilwiki/wiki/Wiki.java +++ b/maeil-wiki/src/main/java/maeilwiki/wiki/domain/Wiki.java @@ -1,4 +1,4 @@ -package maeilwiki.wiki; +package maeilwiki.wiki.domain; import java.time.LocalDateTime; import jakarta.persistence.Column; @@ -15,7 +15,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; import maeilsupport.BaseEntity; -import maeilwiki.member.Member; +import maeilwiki.member.domain.Member; @Entity @Getter diff --git a/maeil-wiki/src/main/java/maeilwiki/wiki/WikiCategory.java b/maeil-wiki/src/main/java/maeilwiki/wiki/domain/WikiCategory.java similarity index 69% rename from maeil-wiki/src/main/java/maeilwiki/wiki/WikiCategory.java rename to maeil-wiki/src/main/java/maeilwiki/wiki/domain/WikiCategory.java index 2f9f997..d876424 100644 --- a/maeil-wiki/src/main/java/maeilwiki/wiki/WikiCategory.java +++ b/maeil-wiki/src/main/java/maeilwiki/wiki/domain/WikiCategory.java @@ -1,6 +1,7 @@ -package maeilwiki.wiki; +package maeilwiki.wiki.domain; import java.util.Arrays; +import java.util.NoSuchElementException; public enum WikiCategory { @@ -10,6 +11,6 @@ public static WikiCategory from(String category) { return Arrays.stream(WikiCategory.values()) .filter(it -> it.name().equalsIgnoreCase(category)) .findFirst() - .orElseThrow(IllegalArgumentException::new); + .orElseThrow(NoSuchElementException::new); } } diff --git a/maeil-wiki/src/main/java/maeilwiki/wiki/WikiRepository.java b/maeil-wiki/src/main/java/maeilwiki/wiki/domain/WikiRepository.java similarity index 78% rename from maeil-wiki/src/main/java/maeilwiki/wiki/WikiRepository.java rename to maeil-wiki/src/main/java/maeilwiki/wiki/domain/WikiRepository.java index af4f0fd..191082a 100644 --- a/maeil-wiki/src/main/java/maeilwiki/wiki/WikiRepository.java +++ b/maeil-wiki/src/main/java/maeilwiki/wiki/domain/WikiRepository.java @@ -1,9 +1,9 @@ -package maeilwiki.wiki; +package maeilwiki.wiki.domain; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; -public interface WikiRepository extends JpaRepository { +public interface WikiRepository extends JpaRepository, WikiRepositoryCustom { Optional findByIdAndDeletedAtIsNull(Long id); } diff --git a/maeil-wiki/src/main/java/maeilwiki/wiki/domain/WikiRepositoryCustom.java b/maeil-wiki/src/main/java/maeilwiki/wiki/domain/WikiRepositoryCustom.java new file mode 100644 index 0000000..b0d57b9 --- /dev/null +++ b/maeil-wiki/src/main/java/maeilwiki/wiki/domain/WikiRepositoryCustom.java @@ -0,0 +1,14 @@ +package maeilwiki.wiki.domain; + +import java.util.Optional; +import maeilwiki.wiki.dto.WikiSummary; +import maeilwiki.wiki.dto.WikiSummaryWithCommentCount; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface WikiRepositoryCustom { + + Optional queryOneById(Long wikiId); + + Page pageByCategory(String category, Pageable pageable); +} diff --git a/maeil-wiki/src/main/java/maeilwiki/wiki/domain/WikiRepositoryImpl.java b/maeil-wiki/src/main/java/maeilwiki/wiki/domain/WikiRepositoryImpl.java new file mode 100644 index 0000000..268eb4c --- /dev/null +++ b/maeil-wiki/src/main/java/maeilwiki/wiki/domain/WikiRepositoryImpl.java @@ -0,0 +1,90 @@ +package maeilwiki.wiki.domain; + +import static maeilwiki.comment.domain.QComment.comment; +import static maeilwiki.member.domain.QMember.member; +import static maeilwiki.wiki.domain.QWiki.wiki; + +import java.util.Optional; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import maeilwiki.member.dto.QMemberThumbnail; +import maeilwiki.wiki.dto.QWikiSummary; +import maeilwiki.wiki.dto.QWikiSummaryWithCommentCount; +import maeilwiki.wiki.dto.WikiSummary; +import maeilwiki.wiki.dto.WikiSummaryWithCommentCount; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.support.PageableExecutionUtils; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Transactional(readOnly = true) +class WikiRepositoryImpl implements WikiRepositoryCustom { + + private final JPAQueryFactory queryFactory; + + @Override + public Optional queryOneById(Long wikiId) { + WikiSummary wikiSummary = queryFactory.select(projectionWikiSummary()) + .from(wiki) + .join(member).on(wiki.member.eq(member)) + .where(wiki.id.eq(wikiId) + .and(isNotDeletedWiki())) + .fetchOne(); + return Optional.ofNullable(wikiSummary); + } + + @Override + public Page pageByCategory(String category, Pageable pageable) { + JPAQuery countQuery = queryFactory.select(wiki.count()) + .from(wiki) + .where(isNotDeletedWiki() + .and(eqCategory(category))); + + JPAQuery resultQuery = queryFactory.select( + new QWikiSummaryWithCommentCount(projectionWikiSummary(), comment.count()) + ) + .from(wiki) + .join(member).on(wiki.member.eq(member)) + .leftJoin(comment).on(wiki.id.eq(comment.wikiId)) + .where(isNotDeletedWiki() + .and(eqCategory(category)) + .and(comment.deletedAt.isNull())) + .groupBy(wiki.id) + .orderBy(wiki.id.desc()) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()); + + return PageableExecutionUtils.getPage(resultQuery.fetch(), pageable, countQuery::fetchOne); + } + + private BooleanExpression isNotDeletedWiki() { + return wiki.deletedAt.isNull(); + } + + private BooleanExpression eqCategory(String category) { + if (category == null || category.equalsIgnoreCase("all")) { + return null; + } + + return wiki.category.eq(WikiCategory.from(category)); + } + + private QWikiSummary projectionWikiSummary() { + return new QWikiSummary( + wiki.id, + wiki.question, + wiki.questionDetail, + wiki.category.stringValue().lower(), + wiki.isAnonymous, + wiki.createdAt, + projectionMemberThumbnail() + ); + } + + private QMemberThumbnail projectionMemberThumbnail() { + return new QMemberThumbnail(member.name, member.profileImageUrl, member.githubUrl); + } +} diff --git a/maeil-wiki/src/main/java/maeilwiki/wiki/dto/WikiSummary.java b/maeil-wiki/src/main/java/maeilwiki/wiki/dto/WikiSummary.java new file mode 100644 index 0000000..524cd1f --- /dev/null +++ b/maeil-wiki/src/main/java/maeilwiki/wiki/dto/WikiSummary.java @@ -0,0 +1,24 @@ +package maeilwiki.wiki.dto; + +import java.time.LocalDateTime; +import com.querydsl.core.annotations.QueryProjection; +import maeilwiki.member.dto.MemberThumbnail; + +public record WikiSummary( + Long id, + String question, + String questionDetail, + String category, + boolean isAnonymous, + LocalDateTime createdAt, + MemberThumbnail owner +) { + + @QueryProjection + public WikiSummary { + } + + public WikiSummary toAnonymousOwner() { + return new WikiSummary(id, question, questionDetail, category, isAnonymous, createdAt, null); + } +} diff --git a/maeil-wiki/src/main/java/maeilwiki/wiki/dto/WikiSummaryWithCommentCount.java b/maeil-wiki/src/main/java/maeilwiki/wiki/dto/WikiSummaryWithCommentCount.java new file mode 100644 index 0000000..1a0bf94 --- /dev/null +++ b/maeil-wiki/src/main/java/maeilwiki/wiki/dto/WikiSummaryWithCommentCount.java @@ -0,0 +1,13 @@ +package maeilwiki.wiki.dto; + +import com.querydsl.core.annotations.QueryProjection; + +public record WikiSummaryWithCommentCount( + WikiSummary wikiSummary, + Long commentCount +) { + + @QueryProjection + public WikiSummaryWithCommentCount { + } +} diff --git a/maeil-wiki/src/test/java/maeilwiki/TestApplication.java b/maeil-wiki/src/test/java/maeilwiki/TestApplication.java new file mode 100644 index 0000000..7279f0b --- /dev/null +++ b/maeil-wiki/src/test/java/maeilwiki/TestApplication.java @@ -0,0 +1,9 @@ +package maeilwiki; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; + +@ConfigurationPropertiesScan +@SpringBootApplication(scanBasePackages = {"maeilwiki", "maeilsupport"}) +public class TestApplication { +} diff --git a/maeil-wiki/src/test/java/maeilwiki/comment/CommentRepositoryTest.java b/maeil-wiki/src/test/java/maeilwiki/comment/CommentRepositoryTest.java deleted file mode 100644 index 45d03a8..0000000 --- a/maeil-wiki/src/test/java/maeilwiki/comment/CommentRepositoryTest.java +++ /dev/null @@ -1,61 +0,0 @@ -package maeilwiki.comment; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; - -import java.util.UUID; -import maeilwiki.member.Member; -import maeilwiki.member.MemberRepository; -import maeilwiki.support.IntegrationTestSupport; -import maeilwiki.wiki.Wiki; -import maeilwiki.wiki.WikiRepository; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; - -class CommentRepositoryTest extends IntegrationTestSupport { - - @Autowired - private WikiRepository wikiRepository; - - @Autowired - private MemberRepository memberRepository; - - @Autowired - private CommentRepository commentRepository; - - @Test - @DisplayName("주어진 위키에 속하는 답변이 존재하는지 조회한다.") - void existsComment() { - Member member = createMember(); - Wiki wiki = createWiki(member); - Wiki noCommentWiki = createWiki(member); - createComment(member, wiki); - Comment comment = createComment(member, noCommentWiki); - comment.remove(); - - assertAll( - () -> assertThat(commentRepository.existsByWikiIdAndDeletedAtIsNull(wiki.getId())).isTrue(), - () -> assertThat(commentRepository.existsByWikiIdAndDeletedAtIsNull(noCommentWiki.getId())).isFalse() - ); - } - - private Member createMember() { - Member member = new Member(UUID.randomUUID().toString(), UUID.randomUUID().toString(), "GITHUB"); - member.setRefreshToken("refresh"); - - return memberRepository.save(member); - } - - private Wiki createWiki(Member member) { - Wiki wiki = new Wiki("question", "backend", false, member); - - return wikiRepository.save(wiki); - } - - private Comment createComment(Member member, Wiki wiki) { - Comment comment = new Comment("answer", false, member, wiki.getId()); - - return commentRepository.save(comment); - } -} diff --git a/maeil-wiki/src/test/java/maeilwiki/comment/CommentServiceTest.java b/maeil-wiki/src/test/java/maeilwiki/comment/application/CommentServiceTest.java similarity index 90% rename from maeil-wiki/src/test/java/maeilwiki/comment/CommentServiceTest.java rename to maeil-wiki/src/test/java/maeilwiki/comment/application/CommentServiceTest.java index 23116b6..e7b7f9c 100644 --- a/maeil-wiki/src/test/java/maeilwiki/comment/CommentServiceTest.java +++ b/maeil-wiki/src/test/java/maeilwiki/comment/application/CommentServiceTest.java @@ -1,4 +1,4 @@ -package maeilwiki.comment; +package maeilwiki.comment.application; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -6,12 +6,16 @@ import java.util.List; import java.util.NoSuchElementException; import java.util.UUID; +import maeilwiki.comment.domain.Comment; +import maeilwiki.comment.domain.CommentLike; +import maeilwiki.comment.domain.CommentLikeRepository; +import maeilwiki.comment.domain.CommentRepository; import maeilwiki.member.Identity; -import maeilwiki.member.Member; -import maeilwiki.member.MemberRepository; +import maeilwiki.member.domain.Member; +import maeilwiki.member.domain.MemberRepository; import maeilwiki.support.IntegrationTestSupport; -import maeilwiki.wiki.Wiki; -import maeilwiki.wiki.WikiRepository; +import maeilwiki.wiki.domain.Wiki; +import maeilwiki.wiki.domain.WikiRepository; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; diff --git a/maeil-wiki/src/test/java/maeilwiki/comment/domain/CommentRepositoryTest.java b/maeil-wiki/src/test/java/maeilwiki/comment/domain/CommentRepositoryTest.java new file mode 100644 index 0000000..e306dbf --- /dev/null +++ b/maeil-wiki/src/test/java/maeilwiki/comment/domain/CommentRepositoryTest.java @@ -0,0 +1,113 @@ +package maeilwiki.comment.domain; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.SoftAssertions.assertSoftly; +import static org.junit.jupiter.api.Assertions.assertAll; + +import java.util.List; +import java.util.UUID; +import maeilwiki.comment.dto.CommentSummary; +import maeilwiki.member.domain.Member; +import maeilwiki.member.domain.MemberRepository; +import maeilwiki.support.IntegrationTestSupport; +import maeilwiki.wiki.domain.Wiki; +import maeilwiki.wiki.domain.WikiRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +class CommentRepositoryTest extends IntegrationTestSupport { + + @Autowired + private WikiRepository wikiRepository; + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private CommentRepository commentRepository; + + @Test + @DisplayName("주어진 위키에 속하는 답변이 존재하는지 조회한다.") + void existsComment() { + Member member = createMember(); + Wiki wiki = createWiki(member); + Wiki noCommentWiki = createWiki(member); + createComment(member, wiki); + + assertAll( + () -> assertThat(commentRepository.existsByWikiIdAndDeletedAtIsNull(wiki.getId())).isTrue(), + () -> assertThat(commentRepository.existsByWikiIdAndDeletedAtIsNull(noCommentWiki.getId())).isFalse() + ); + } + + @Test + @DisplayName("위키의 댓글을 조회한다.") + void queryAllByWikiId() { + // given + Member prin = createMember(); + Member atom = createMember(); + + Wiki wiki1 = createWiki(prin); + Wiki wiki2 = createWiki(prin); + + Comment comment1 = createComment(atom, wiki1); + Comment comment2 = createComment(atom, wiki1); + createComment(atom, wiki2); + + // when + List commentSummary = commentRepository.queryAllByWikiId(wiki1.getId()); + + // then + assertSoftly(softAssertions -> { + softAssertions.assertThat(commentSummary).hasSize(2); + + softAssertions.assertThat(commentSummary.get(0).id()).isEqualTo(comment1.getId()); + softAssertions.assertThat(commentSummary.get(0).answer()).isEqualTo(comment1.getAnswer()); + softAssertions.assertThat(commentSummary.get(0).isAnonymous()).isEqualTo(comment1.isAnonymous()); + softAssertions.assertThat(commentSummary.get(0).owner().name()).isEqualTo(comment1.getMember().getName()); + softAssertions.assertThat(commentSummary.get(0).owner().profileImage()).isEqualTo(comment1.getMember().getProfileImageUrl()); + softAssertions.assertThat(commentSummary.get(0).owner().github()).isEqualTo(comment1.getMember().getGithubUrl()); + + softAssertions.assertThat(commentSummary.get(1).id()).isEqualTo(comment2.getId()); + softAssertions.assertThat(commentSummary.get(1).answer()).isEqualTo(comment2.getAnswer()); + softAssertions.assertThat(commentSummary.get(1).isAnonymous()).isEqualTo(comment2.isAnonymous()); + softAssertions.assertThat(commentSummary.get(1).owner().name()).isEqualTo(comment2.getMember().getName()); + softAssertions.assertThat(commentSummary.get(1).owner().profileImage()).isEqualTo(comment2.getMember().getProfileImageUrl()); + softAssertions.assertThat(commentSummary.get(1).owner().github()).isEqualTo(comment2.getMember().getGithubUrl()); + }); + } + + @Test + @DisplayName("위키에 댓글이 없는 경우 빈 리스트를 반환한다.") + void emptyResult() { + // given + Member atom = createMember(); + Wiki wiki = createWiki(atom); + + // when + List commentSummary = commentRepository.queryAllByWikiId(wiki.getId()); + + // then + assertThat(commentSummary).isEmpty(); + } + + private Member createMember() { + Member member = new Member(UUID.randomUUID().toString(), UUID.randomUUID().toString(), "GITHUB"); + member.setRefreshToken("refresh"); + + return memberRepository.save(member); + } + + private Wiki createWiki(Member member) { + Wiki wiki = new Wiki("question", "backend", false, member); + + return wikiRepository.save(wiki); + } + + private Comment createComment(Member member, Wiki wiki) { + Comment comment = new Comment("answer", false, member, wiki.getId()); + + return commentRepository.save(comment); + } +} diff --git a/maeil-wiki/src/test/java/maeilwiki/comment/CommentTest.java b/maeil-wiki/src/test/java/maeilwiki/comment/domain/CommentTest.java similarity index 93% rename from maeil-wiki/src/test/java/maeilwiki/comment/CommentTest.java rename to maeil-wiki/src/test/java/maeilwiki/comment/domain/CommentTest.java index 72a3a3b..a4d674b 100644 --- a/maeil-wiki/src/test/java/maeilwiki/comment/CommentTest.java +++ b/maeil-wiki/src/test/java/maeilwiki/comment/domain/CommentTest.java @@ -1,10 +1,10 @@ -package maeilwiki.comment; +package maeilwiki.comment.domain; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.mock; -import maeilwiki.member.Member; +import maeilwiki.member.domain.Member; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/maeil-wiki/src/test/java/maeilwiki/member/MemberTokenAuthorizerTest.java b/maeil-wiki/src/test/java/maeilwiki/member/MemberTokenAuthorizerTest.java index 5ee09fc..d6ef340 100644 --- a/maeil-wiki/src/test/java/maeilwiki/member/MemberTokenAuthorizerTest.java +++ b/maeil-wiki/src/test/java/maeilwiki/member/MemberTokenAuthorizerTest.java @@ -5,6 +5,9 @@ import java.nio.charset.StandardCharsets; import java.util.Base64; +import maeilwiki.member.domain.Member; +import maeilwiki.member.domain.MemberRepository; +import maeilwiki.member.domain.Provider; import maeilwiki.support.IntegrationTestSupport; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/maeil-wiki/src/test/java/maeilwiki/member/MemberTokenGeneratorTest.java b/maeil-wiki/src/test/java/maeilwiki/member/MemberTokenGeneratorTest.java index 0fa2f20..0897556 100644 --- a/maeil-wiki/src/test/java/maeilwiki/member/MemberTokenGeneratorTest.java +++ b/maeil-wiki/src/test/java/maeilwiki/member/MemberTokenGeneratorTest.java @@ -4,6 +4,8 @@ import static org.junit.jupiter.api.Assertions.assertAll; import java.util.Base64; +import maeilwiki.member.domain.Member; +import maeilwiki.member.domain.MemberRepository; import maeilwiki.support.IntegrationTestSupport; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/maeil-wiki/src/test/java/maeilwiki/member/MemberServiceTest.java b/maeil-wiki/src/test/java/maeilwiki/member/application/MemberServiceTest.java similarity index 95% rename from maeil-wiki/src/test/java/maeilwiki/member/MemberServiceTest.java rename to maeil-wiki/src/test/java/maeilwiki/member/application/MemberServiceTest.java index 3cac40f..5cf761c 100644 --- a/maeil-wiki/src/test/java/maeilwiki/member/MemberServiceTest.java +++ b/maeil-wiki/src/test/java/maeilwiki/member/application/MemberServiceTest.java @@ -1,4 +1,4 @@ -package maeilwiki.member; +package maeilwiki.member.application; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -6,6 +6,8 @@ import java.util.List; import java.util.NoSuchElementException; +import maeilwiki.member.domain.Member; +import maeilwiki.member.domain.MemberRepository; import maeilwiki.support.IntegrationTestSupport; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/maeil-wiki/src/test/java/maeilwiki/member/github/GithubMemberTest.java b/maeil-wiki/src/test/java/maeilwiki/member/github/GithubMemberTest.java index 09324ef..f03bedb 100644 --- a/maeil-wiki/src/test/java/maeilwiki/member/github/GithubMemberTest.java +++ b/maeil-wiki/src/test/java/maeilwiki/member/github/GithubMemberTest.java @@ -2,7 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import maeilwiki.member.Member; +import maeilwiki.member.domain.Member; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/maeil-wiki/src/test/java/maeilwiki/support/IntegrationTestSupport.java b/maeil-wiki/src/test/java/maeilwiki/support/IntegrationTestSupport.java index 436f5c9..06eec7c 100644 --- a/maeil-wiki/src/test/java/maeilwiki/support/IntegrationTestSupport.java +++ b/maeil-wiki/src/test/java/maeilwiki/support/IntegrationTestSupport.java @@ -3,25 +3,24 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment; -import maeilwiki.WikiConfiguration; import maeilwiki.member.github.GithubClient; import maeilwiki.member.github.GithubMember; +import maeilwiki.support.extension.DatabaseCleanerExtension; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; -import org.springframework.data.jpa.repository.config.EnableJpaAuditing; -import org.springframework.transaction.annotation.Transactional; -@Transactional -@SpringBootTest(classes = WikiConfiguration.class) +@ExtendWith(DatabaseCleanerExtension.class) +@SpringBootTest(webEnvironment = WebEnvironment.NONE) @Import(IntegrationTestSupport.TestConfig.class) public abstract class IntegrationTestSupport { - @EnableJpaAuditing @TestConfiguration - public static class TestConfig { + static class TestConfig { @Bean public GithubClient githubClient() { diff --git a/maeil-wiki/src/test/java/maeilwiki/support/extension/DatabaseCleanerExtension.java b/maeil-wiki/src/test/java/maeilwiki/support/extension/DatabaseCleanerExtension.java new file mode 100644 index 0000000..54891ee --- /dev/null +++ b/maeil-wiki/src/test/java/maeilwiki/support/extension/DatabaseCleanerExtension.java @@ -0,0 +1,47 @@ +package maeilwiki.support.extension; + +import java.util.List; +import jakarta.persistence.EntityManager; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.springframework.context.ApplicationContext; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.transaction.support.TransactionTemplate; + +public class DatabaseCleanerExtension implements BeforeEachCallback { + + @Override + public void beforeEach(ExtensionContext extensionContext) { + ApplicationContext context = SpringExtension.getApplicationContext(extensionContext); + cleanup(context); + } + + private void cleanup(ApplicationContext context) { + EntityManager em = context.getBean(EntityManager.class); + TransactionTemplate transactionTemplate = context.getBean(TransactionTemplate.class); + + transactionTemplate.execute(action -> { + em.clear(); + truncateTables(em); + return null; + }); + } + + private void truncateTables(EntityManager em) { + em.createNativeQuery("SET REFERENTIAL_INTEGRITY FALSE").executeUpdate(); + for (String tableName : findTableNames(em)) { + em.createNativeQuery("TRUNCATE TABLE %s RESTART IDENTITY".formatted(tableName)).executeUpdate(); + } + em.createNativeQuery("SET REFERENTIAL_INTEGRITY TRUE").executeUpdate(); + } + + @SuppressWarnings("unchecked") + private List findTableNames(EntityManager em) { + String tableNameSelectQuery = """ + SELECT TABLE_NAME + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_SCHEMA = 'PUBLIC' + """; + return em.createNativeQuery(tableNameSelectQuery).getResultList(); + } +} diff --git a/maeil-wiki/src/test/java/maeilwiki/wiki/WikiServiceTest.java b/maeil-wiki/src/test/java/maeilwiki/wiki/WikiServiceTest.java deleted file mode 100644 index ed71841..0000000 --- a/maeil-wiki/src/test/java/maeilwiki/wiki/WikiServiceTest.java +++ /dev/null @@ -1,98 +0,0 @@ -package maeilwiki.wiki; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import java.util.NoSuchElementException; -import java.util.UUID; -import maeilwiki.comment.Comment; -import maeilwiki.comment.CommentRepository; -import maeilwiki.comment.CommentRequest; -import maeilwiki.member.Identity; -import maeilwiki.member.Member; -import maeilwiki.member.MemberRepository; -import maeilwiki.support.IntegrationTestSupport; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; - -class WikiServiceTest extends IntegrationTestSupport { - - @Autowired - private WikiRepository wikiRepository; - - @Autowired - private MemberRepository memberRepository; - - @Autowired - private CommentRepository commentRepository; - - @Autowired - private WikiService wikiService; - - @Test - @DisplayName("존재하지 않는 위키에 답변을 작성할 수 없다.") - void notfound() { - CommentRequest request = new CommentRequest("답변을 작성합니다.", false); - Long unknownWikiId = -1L; - Identity identity = new Identity(1L); - - assertThatThrownBy(() -> wikiService.comment(identity, request, unknownWikiId)) - .isInstanceOf(NoSuchElementException.class); - } - - @Test - @DisplayName("답변이 존재하는 위키는 삭제할 수 없다.") - void cantRemove() { - Member member = createMember(); - Wiki wiki = createWiki(member); - Comment comment = createComment(member, wiki); - Identity identity = new Identity(member.getId()); - - assertThatThrownBy(() -> wikiService.remove(identity, wiki.getId())) - .isInstanceOf(IllegalStateException.class) - .hasMessage("답변이 존재하는 위키는 삭제할 수 없습니다."); - } - - @Test - @DisplayName("존재하지 않는 위키는 삭제할 수 없다.") - void cantRemoveUnknownWiki() { - Member member = createMember(); - Identity identity = new Identity(member.getId()); - Long unknownWikiId = -1L; - - assertThatThrownBy(() -> wikiService.remove(identity, unknownWikiId)) - .isInstanceOf(NoSuchElementException.class); - } - - @Test - @DisplayName("자신의 위키만 삭제할 수 있다.") - void cantRemoveOtherWiki() { - Member member = createMember(); - Wiki wiki = createWiki(member); - Member otherMember = createMember(); - Identity otherMemberIdentity = new Identity(otherMember.getId()); - - assertThatThrownBy(() -> wikiService.remove(otherMemberIdentity, wiki.getId())) - .isInstanceOf(IllegalStateException.class) - .hasMessage("자신의 위키만 삭제할 수 있습니다."); - } - - private Member createMember() { - Member member = new Member(UUID.randomUUID().toString(), UUID.randomUUID().toString(), "GITHUB"); - member.setRefreshToken("refresh"); - - return memberRepository.save(member); - } - - private Wiki createWiki(Member member) { - Wiki wiki = new Wiki("question", "backend", false, member); - - return wikiRepository.save(wiki); - } - - private Comment createComment(Member member, Wiki wiki) { - Comment comment = new Comment("answer", false, member, wiki.getId()); - - return commentRepository.save(comment); - } -} diff --git a/maeil-wiki/src/test/java/maeilwiki/wiki/application/WikiServiceTest.java b/maeil-wiki/src/test/java/maeilwiki/wiki/application/WikiServiceTest.java new file mode 100644 index 0000000..240b274 --- /dev/null +++ b/maeil-wiki/src/test/java/maeilwiki/wiki/application/WikiServiceTest.java @@ -0,0 +1,232 @@ +package maeilwiki.wiki.application; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +import java.util.NoSuchElementException; +import java.util.UUID; +import maeilsupport.PaginationResponse; +import maeilwiki.comment.application.CommentRequest; +import maeilwiki.comment.domain.Comment; +import maeilwiki.comment.domain.CommentRepository; +import maeilwiki.member.Identity; +import maeilwiki.member.domain.Member; +import maeilwiki.member.domain.MemberRepository; +import maeilwiki.support.IntegrationTestSupport; +import maeilwiki.wiki.domain.Wiki; +import maeilwiki.wiki.domain.WikiRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.PageRequest; + +class WikiServiceTest extends IntegrationTestSupport { + + @Autowired + private WikiRepository wikiRepository; + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private CommentRepository commentRepository; + + @Autowired + private WikiService wikiService; + + @Test + @DisplayName("존재하지 않는 위키에 답변을 작성할 수 없다.") + void notfound() { + CommentRequest request = new CommentRequest("답변을 작성합니다.", false); + Long unknownWikiId = -1L; + Identity identity = new Identity(1L); + + assertThatThrownBy(() -> wikiService.comment(identity, request, unknownWikiId)) + .isInstanceOf(NoSuchElementException.class); + } + + @Test + @DisplayName("답변이 존재하는 위키는 삭제할 수 없다.") + void cantRemove() { + Member member = createMember(); + Wiki wiki = createWiki(member); + Comment comment = createComment(member, wiki); + Identity identity = new Identity(member.getId()); + + assertThatThrownBy(() -> wikiService.remove(identity, wiki.getId())) + .isInstanceOf(IllegalStateException.class) + .hasMessage("답변이 존재하는 위키는 삭제할 수 없습니다."); + } + + @Test + @DisplayName("존재하지 않는 위키는 삭제할 수 없다.") + void cantRemoveUnknownWiki() { + Member member = createMember(); + Identity identity = new Identity(member.getId()); + Long unknownWikiId = -1L; + + assertThatThrownBy(() -> wikiService.remove(identity, unknownWikiId)) + .isInstanceOf(NoSuchElementException.class); + } + + @Test + @DisplayName("자신의 위키만 삭제할 수 있다.") + void cantRemoveOtherWiki() { + Member member = createMember(); + Wiki wiki = createWiki(member); + Member otherMember = createMember(); + Identity otherMemberIdentity = new Identity(otherMember.getId()); + + assertThatThrownBy(() -> wikiService.remove(otherMemberIdentity, wiki.getId())) + .isInstanceOf(IllegalStateException.class) + .hasMessage("자신의 위키만 삭제할 수 있습니다."); + } + + @Test + @DisplayName("Wiki 아이디로 Wiki 단건을 조회한다.") + void getWikiById() { + // given + Member prin = createMember(); + Member atom = createMember(); + Wiki wiki1 = createWiki(prin); + Wiki wiki2 = createWiki(prin); + Comment wiki1Comment1 = createComment(atom, wiki1); + createComment(atom, wiki2); + Comment wiki1Comment2 = createComment(atom, wiki1); + createComment(atom, wiki2); + + // when + WikiResponse wikiResponse = wikiService.getWikiById(wiki1.getId()); + + // then + assertSoftly(softAssertions -> { + softAssertions.assertThat(wikiResponse.id()).isEqualTo(wiki1.getId()); + softAssertions.assertThat(wikiResponse.question()).isEqualTo(wiki1.getQuestion()); + softAssertions.assertThat(wikiResponse.questionDetail()).isEqualTo(wiki1.getQuestionDetail()); + softAssertions.assertThat(wikiResponse.category()).isEqualTo(wiki1.getCategory().toString().toLowerCase()); + softAssertions.assertThat(wikiResponse.owner().name()).isEqualTo(wiki1.getMember().getName()); + softAssertions.assertThat(wikiResponse.owner().profileImage()).isEqualTo(wiki1.getMember().getProfileImageUrl()); + softAssertions.assertThat(wikiResponse.owner().github()).isEqualTo(wiki1.getMember().getGithubUrl()); + softAssertions.assertThat(wikiResponse.comments()).hasSize(2); + softAssertions.assertThat(wikiResponse.comments().get(0).id()).isEqualTo(wiki1Comment1.getId()); + softAssertions.assertThat(wikiResponse.comments().get(1).id()).isEqualTo(wiki1Comment2.getId()); + }); + } + + @Test + @DisplayName("Wiki가 익명이면 Wiki 작성자를 null 처리한다.") + void getAnonymousWikiById() { + // given + boolean isAnonymousWiki = true; + Member prin = createMember(); + Wiki wiki = createWiki(prin, isAnonymousWiki); + createComment(prin, wiki); + + // when + WikiResponse wikiResponse = wikiService.getWikiById(wiki.getId()); + + // then + assertThat(wikiResponse.owner()).isNull(); + } + + @Test + @DisplayName("Wiki의 Comment가 익명이면 Comment 작성자를 null 처리한다.") + void getWikiWithAnonymousCommentById() { + // given + boolean isAnonymousComment = true; + Member atom = createMember(); + Wiki wiki = createWiki(atom); + createComment(atom, wiki, isAnonymousComment); + + // when + WikiResponse wikiResponse = wikiService.getWikiById(wiki.getId()); + + // then + assertThat(wikiResponse.comments().get(0).owner()).isNull(); + } + + @Test + @DisplayName("카테고리에 해당하는 Wiki 페이지를 조회한다.") + void pageByCategory() { + // given + Member prin = createMember(); + Member atom = createMember(); + Wiki wiki1 = createWiki(prin, "FRONTEND"); + Wiki wiki2 = createWiki(prin, "FRONTEND"); + createComment(atom, wiki1); + createComment(atom, wiki1); + createComment(atom, wiki1); + createComment(atom, wiki2); + createComment(atom, wiki2); + + // when + PaginationResponse wikiResponses = wikiService.pageByCategory("FRONTEND", PageRequest.of(0, 3)); + + // then + assertSoftly(softAssertions -> { + softAssertions.assertThat(wikiResponses.isLastPage()).isTrue(); + softAssertions.assertThat(wikiResponses.totalPage()).isEqualTo(1); + softAssertions.assertThat(wikiResponses.data()).hasSize(2); + softAssertions.assertThat(wikiResponses.data().get(0).id()).isEqualTo(wiki2.getId()); + softAssertions.assertThat(wikiResponses.data().get(0).commentCount()).isEqualTo(2); + softAssertions.assertThat(wikiResponses.data().get(1).id()).isEqualTo(wiki1.getId()); + softAssertions.assertThat(wikiResponses.data().get(1).commentCount()).isEqualTo(3); + }); + } + + @Test + @DisplayName("각각의 Wiki마다 익명 여부에 따라 Wiki 작성자를 null 처리한다.") + void pageByCategoryWithAnonymousWiki() { + // given + Member prin = createMember(); + Wiki anonymousWiki = createWiki(prin, true); + Wiki nonanonymousWiki = createWiki(prin, false); + + // when + PaginationResponse wikiResponses = wikiService.pageByCategory("all", PageRequest.of(0, 2)); + + // then + assertSoftly(softAssertions -> { + softAssertions.assertThat(wikiResponses.data().get(0).id()).isEqualTo(nonanonymousWiki.getId()); + softAssertions.assertThat(wikiResponses.data().get(0).owner()).isNotNull(); + softAssertions.assertThat(wikiResponses.data().get(1).id()).isEqualTo(anonymousWiki.getId()); + softAssertions.assertThat(wikiResponses.data().get(1).owner()).isNull(); + }); + } + + private Member createMember() { + Member member = new Member(UUID.randomUUID().toString(), UUID.randomUUID().toString(), "GITHUB"); + member.setRefreshToken("refresh"); + + return memberRepository.save(member); + } + + private Wiki createWiki(Member member) { + return createWiki(member, "backend", false); + } + + private Wiki createWiki(Member member, String category) { + return createWiki(member, category, false); + } + + private Wiki createWiki(Member member, boolean isAnonymous) { + return createWiki(member, "backend", isAnonymous); + } + + private Wiki createWiki(Member member, String category, boolean isAnonymous) { + Wiki wiki = new Wiki("question", category, isAnonymous, member); + + return wikiRepository.save(wiki); + } + + private Comment createComment(Member member, Wiki wiki) { + return createComment(member, wiki, false); + } + + private Comment createComment(Member member, Wiki wiki, boolean isAnonymous) { + Comment comment = new Comment("answer", isAnonymous, member, wiki.getId()); + + return commentRepository.save(comment); + } +} diff --git a/maeil-wiki/src/test/java/maeilwiki/wiki/domain/WikiRepositoryTest.java b/maeil-wiki/src/test/java/maeilwiki/wiki/domain/WikiRepositoryTest.java new file mode 100644 index 0000000..0682850 --- /dev/null +++ b/maeil-wiki/src/test/java/maeilwiki/wiki/domain/WikiRepositoryTest.java @@ -0,0 +1,168 @@ +package maeilwiki.wiki.domain; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +import java.util.Optional; +import java.util.UUID; +import maeilwiki.comment.domain.Comment; +import maeilwiki.comment.domain.CommentRepository; +import maeilwiki.member.domain.Member; +import maeilwiki.member.domain.MemberRepository; +import maeilwiki.support.IntegrationTestSupport; +import maeilwiki.wiki.dto.WikiSummary; +import maeilwiki.wiki.dto.WikiSummaryWithCommentCount; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +class WikiRepositoryTest extends IntegrationTestSupport { + + @Autowired + private WikiRepository wikiRepository; + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private CommentRepository commentRepository; + + @Test + @DisplayName("Wiki 아이디로 WikiSummary를 조회한다.") + void queryOneById() { + // given + Member prin = createMember(); + Wiki wiki = createWiki(prin); + + // when + WikiSummary wikiSummary = wikiRepository.queryOneById(wiki.getId()).orElseThrow(); + + // then + assertSoftly(softAssertions -> { + softAssertions.assertThat(wikiSummary.id()).isEqualTo(wiki.getId()); + softAssertions.assertThat(wikiSummary.question()).isEqualTo(wiki.getQuestion()); + softAssertions.assertThat(wikiSummary.questionDetail()).isEqualTo(wiki.getQuestionDetail()); + softAssertions.assertThat(wikiSummary.category()).isEqualTo(wiki.getCategory().toString().toLowerCase()); + softAssertions.assertThat(wikiSummary.isAnonymous()).isEqualTo(wiki.isAnonymous()); + softAssertions.assertThat(wikiSummary.owner().name()).isEqualTo(wiki.getMember().getName()); + softAssertions.assertThat(wikiSummary.owner().profileImage()).isEqualTo(wiki.getMember().getProfileImageUrl()); + softAssertions.assertThat(wikiSummary.owner().github()).isEqualTo(wiki.getMember().getGithubUrl()); + }); + } + + @Test + @DisplayName("존재하지 않은 Wiki 아이디로 조회하면 빈 Optional을 반환한다.") + void queryOneByIdNotFound() { + // given + Long notFoundWikiId = 9999L; + + // when + Optional wikiSummary = wikiRepository.queryOneById(notFoundWikiId); + + // then + assertThat(wikiSummary).isEmpty(); + } + + @Test + @DisplayName("카테고리에 해당하는 Wiki 페이지를 id 기준 내림차순으로 조회한다.") + void pageByCategory() { + // given + Member prin = createMember(); + Member atom = createMember(); + Wiki wiki1 = createWiki(prin, "FRONTEND"); + createComment(atom, wiki1); + createComment(atom, wiki1); + createComment(atom, wiki1); + createComment(atom, wiki1); + Wiki wiki2 = createWiki(prin, "FRONTEND"); + createComment(atom, wiki2); + createComment(atom, wiki2); + createComment(atom, wiki2); + createWiki(prin, "BACKEND"); + createWiki(prin, "FRONTEND"); + createWiki(prin, "FRONTEND"); + Pageable pageable = PageRequest.of(1, 2); + + // when + Page wikiSummaryPage = wikiRepository.pageByCategory("FRONTEND", pageable); + + // then + assertSoftly(softAssertions -> { + softAssertions.assertThat(wikiSummaryPage.getTotalElements()).isEqualTo(4); + softAssertions.assertThat(wikiSummaryPage.getTotalPages()).isEqualTo(2); + softAssertions.assertThat(wikiSummaryPage.getSize()).isEqualTo(2); + softAssertions.assertThat(wikiSummaryPage.getContent().get(0).wikiSummary().id()).isEqualTo(wiki2.getId()); + softAssertions.assertThat(wikiSummaryPage.getContent().get(0).commentCount()).isEqualTo(3); + softAssertions.assertThat(wikiSummaryPage.getContent().get(1).wikiSummary().id()).isEqualTo(wiki1.getId()); + softAssertions.assertThat(wikiSummaryPage.getContent().get(1).commentCount()).isEqualTo(4); + }); + } + + @Test + @DisplayName("카테고리가 all이면 모든 카테고리의 Wiki 페이지를 조회한다.") + void pageByDefaultCategory() { + // given + Member prin = createMember(); + Member atom = createMember(); + Wiki wiki1 = createWiki(prin, "FRONTEND"); + createComment(atom, wiki1); + createComment(atom, wiki1); + Wiki wiki2 = createWiki(prin, "FRONTEND"); + createComment(atom, wiki2); + Wiki wiki3 = createWiki(prin, "BACKEND"); + createComment(atom, wiki3); + createComment(atom, wiki3); + createComment(atom, wiki3); + Wiki wiki4 = createWiki(prin, "FRONTEND"); + createComment(atom, wiki4); + createComment(atom, wiki4); + Wiki wiki5 = createWiki(prin, "FRONTEND"); + createComment(atom, wiki5); + createComment(atom, wiki5); + createComment(atom, wiki5); + createComment(atom, wiki5); + Pageable pageable = PageRequest.of(0, 3); + + // when + Page wikiSummaryPage = wikiRepository.pageByCategory("all", pageable); + + // then + assertSoftly(softAssertions -> { + softAssertions.assertThat(wikiSummaryPage.getTotalElements()).isEqualTo(5); + softAssertions.assertThat(wikiSummaryPage.getTotalPages()).isEqualTo(2); + softAssertions.assertThat(wikiSummaryPage.getSize()).isEqualTo(3); + softAssertions.assertThat(wikiSummaryPage.getContent().get(0).wikiSummary().id()).isEqualTo(wiki5.getId()); + softAssertions.assertThat(wikiSummaryPage.getContent().get(0).commentCount()).isEqualTo(4); + softAssertions.assertThat(wikiSummaryPage.getContent().get(1).wikiSummary().id()).isEqualTo(wiki4.getId()); + softAssertions.assertThat(wikiSummaryPage.getContent().get(1).commentCount()).isEqualTo(2); + softAssertions.assertThat(wikiSummaryPage.getContent().get(2).wikiSummary().id()).isEqualTo(wiki3.getId()); + softAssertions.assertThat(wikiSummaryPage.getContent().get(2).commentCount()).isEqualTo(3); + }); + } + + private Member createMember() { + Member member = new Member(UUID.randomUUID().toString(), UUID.randomUUID().toString(), "GITHUB"); + member.setRefreshToken("refresh"); + + return memberRepository.save(member); + } + + private Wiki createWiki(Member member) { + return createWiki(member, "backend"); + } + + private Wiki createWiki(Member member, String category) { + Wiki wiki = new Wiki("question", category, false, member); + + return wikiRepository.save(wiki); + } + + private Comment createComment(Member member, Wiki wiki) { + Comment comment = new Comment("answer", false, member, wiki.getId()); + + return commentRepository.save(comment); + } +} diff --git a/maeil-wiki/src/test/java/maeilwiki/wiki/WikiTest.java b/maeil-wiki/src/test/java/maeilwiki/wiki/domain/WikiTest.java similarity index 93% rename from maeil-wiki/src/test/java/maeilwiki/wiki/WikiTest.java rename to maeil-wiki/src/test/java/maeilwiki/wiki/domain/WikiTest.java index 482f033..812639b 100644 --- a/maeil-wiki/src/test/java/maeilwiki/wiki/WikiTest.java +++ b/maeil-wiki/src/test/java/maeilwiki/wiki/domain/WikiTest.java @@ -1,10 +1,10 @@ -package maeilwiki.wiki; +package maeilwiki.wiki.domain; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.mock; -import maeilwiki.member.Member; +import maeilwiki.member.domain.Member; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/maeil-wiki/src/test/resources/application.yml b/maeil-wiki/src/test/resources/application.yml index f039f49..e466229 100644 --- a/maeil-wiki/src/test/resources/application.yml +++ b/maeil-wiki/src/test/resources/application.yml @@ -6,9 +6,9 @@ spring: ddl-auto: create properties: hibernate: - auto_quote_keyword: true format_sql: true - show-sql: true + show_sql: true + highlight_sql: true sql: init: mode: never @@ -20,3 +20,14 @@ token: client: secret: clientSecret + +logging: + level: + org: + hibernate: + orm: + jdbc: + bind: TRACE + springframework: + transaction: + interceptor: TRACE