diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f89a2eb..0380583 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,6 +9,7 @@ on: permissions: contents: read + pull-requests: write concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -36,4 +37,12 @@ jobs: run: chmod +x gradlew - name: gradlew test - run: ./gradlew test \ No newline at end of file + run: ./gradlew test + + - name : add coverage to PR + id: jacoco + uses: madrapps/jacoco-report@v1.7.2 + with: + paths: | + ${{ github.workspace }}/**/build/reports/jacoco/test/jacocoTestReport.xml + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/build.gradle b/build.gradle index 02674c0..af912e5 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,7 @@ plugins { id 'java' id 'org.springframework.boot' version '3.4.6-SNAPSHOT' id 'io.spring.dependency-management' version '1.1.7' + id 'jacoco' } group = 'com.specialwarriors' @@ -92,4 +93,55 @@ dependencies { tasks.named('test') { useJUnitPlatform() + finalizedBy jacocoTestReport +} + +jacoco { + toolVersion = "0.8.11" +} + +def jacocoExcludePatterns = [ + 'com/specialwarriors/conal/ConalApplication.class', + '**/common/**', + '**/exception/**', + '**/dto/**', + '**/config/**', + '**/enums/**', + '**/converter/**', + '**/test/**' +] + +for (qPattern in '**/QA'..'**/QZ') { + jacocoExcludePatterns.add(qPattern + '*') +} + +jacocoTestReport { + reports { + xml.required = true + csv.required = true + html.required = true + } + + afterEvaluate { + classDirectories.setFrom(files(classDirectories.files.collect { + fileTree(dir: it, excludes: jacocoExcludePatterns) + })) + } +} + +jacocoTestCoverageVerification { + violationRules { + + rule { + enabled = true + element = 'CLASS' + + // 커버리지 제외 범위 + excludes = jacocoExcludePatterns + } + } +} + +clean { + delete file('src/main/generated') } diff --git a/src/main/java/com/specialwarriors/conal/github_repo/domain/GithubRepo.java b/src/main/java/com/specialwarriors/conal/github_repo/domain/GithubRepo.java index fa46cce..02b8678 100644 --- a/src/main/java/com/specialwarriors/conal/github_repo/domain/GithubRepo.java +++ b/src/main/java/com/specialwarriors/conal/github_repo/domain/GithubRepo.java @@ -18,6 +18,7 @@ import java.util.ArrayList; import java.util.List; import lombok.AccessLevel; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; @@ -25,6 +26,7 @@ @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) +@EqualsAndHashCode(of = "id") public class GithubRepo { @Id diff --git a/src/main/java/com/specialwarriors/conal/github_repo/exception/GithubRepoException.java b/src/main/java/com/specialwarriors/conal/github_repo/exception/GithubRepoException.java index 4275225..f87d7ed 100644 --- a/src/main/java/com/specialwarriors/conal/github_repo/exception/GithubRepoException.java +++ b/src/main/java/com/specialwarriors/conal/github_repo/exception/GithubRepoException.java @@ -9,9 +9,9 @@ @RequiredArgsConstructor public enum GithubRepoException implements BaseException { - UNAUTHORIZED_GITHUBREPO_ACCESS(HttpStatus.FORBIDDEN, "리포지토리 접근 권한이 없습니다"), - NOT_FOUND_GITHUBREPO(HttpStatus.NOT_FOUND, "깃허브 리포지토리를 찾을 수 없습니다"), - NOT_FOUND_GITHUBEMAIL(HttpStatus.NOT_FOUND, "기여자 이메일이 없습니다"), + UNAUTHORIZED_GITHUB_REPO_ACCESS(HttpStatus.FORBIDDEN, "리포지토리 접근 권한이 없습니다"), + GITHUB_REPO_NOT_FOUND(HttpStatus.NOT_FOUND, "깃허브 리포지토리를 찾을 수 없습니다"), + CONTRIBUTOR_EMAIL_NOT_FOUND(HttpStatus.NOT_FOUND, "기여자 이메일이 없습니다"), INVALID_URL(HttpStatus.BAD_REQUEST, "잘못된 URL 입니다."); private final HttpStatus status; diff --git a/src/main/java/com/specialwarriors/conal/github_repo/service/GithubRepoQuery.java b/src/main/java/com/specialwarriors/conal/github_repo/service/GithubRepoQuery.java index e19570f..438f4b0 100644 --- a/src/main/java/com/specialwarriors/conal/github_repo/service/GithubRepoQuery.java +++ b/src/main/java/com/specialwarriors/conal/github_repo/service/GithubRepoQuery.java @@ -4,6 +4,8 @@ import com.specialwarriors.conal.github_repo.domain.GithubRepo; import com.specialwarriors.conal.github_repo.exception.GithubRepoException; import com.specialwarriors.conal.github_repo.repository.GithubRepoRepository; +import com.specialwarriors.conal.user.domain.User; +import com.specialwarriors.conal.user.service.UserQuery; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; @@ -12,29 +14,25 @@ public class GithubRepoQuery { private final GithubRepoRepository githubRepoRepository; + private final UserQuery userQuery; - public GithubRepo findByRepositoryId(Long repositoryId) { - return githubRepoRepository.findById(repositoryId).orElseThrow(() -> - new GeneralException(GithubRepoException.NOT_FOUND_GITHUBREPO) - ); - } + public GithubRepo findByUserIdAndRepositoryId(long userId, long repositoryId) { - public GithubRepo findByUserIdAndRepositoryId(Long userId, Long repositoryId) { + GithubRepo githubRepo = githubRepoRepository.findById(repositoryId) + .orElseThrow(() -> new GeneralException(GithubRepoException.GITHUB_REPO_NOT_FOUND)); - GithubRepo githubRepo = githubRepoRepository.findById(repositoryId).orElseThrow(() -> - new GeneralException(GithubRepoException.NOT_FOUND_GITHUBREPO) - ); + User user = userQuery.findById(userId); - if (!userId.equals(githubRepo.getUser().getId())) { - throw new GeneralException(GithubRepoException.UNAUTHORIZED_GITHUBREPO_ACCESS); + if (user.notHasGithubRepo(githubRepo)) { + throw new GeneralException(GithubRepoException.UNAUTHORIZED_GITHUB_REPO_ACCESS); } return githubRepo; } - public GithubRepo findByRepositoryId(long repositoryId) { + public GithubRepo findById(long repositoryId) { return githubRepoRepository.findById(repositoryId) - .orElseThrow(() -> new GeneralException(GithubRepoException.NOT_FOUND_GITHUBREPO)); + .orElseThrow(() -> new GeneralException(GithubRepoException.GITHUB_REPO_NOT_FOUND)); } } diff --git a/src/main/java/com/specialwarriors/conal/github_repo/service/GithubRepoService.java b/src/main/java/com/specialwarriors/conal/github_repo/service/GithubRepoService.java index 73bc5e5..5b8399c 100644 --- a/src/main/java/com/specialwarriors/conal/github_repo/service/GithubRepoService.java +++ b/src/main/java/com/specialwarriors/conal/github_repo/service/GithubRepoService.java @@ -65,7 +65,7 @@ public GithubRepoCreateResponse createGithubRepo(Long userId, GithubRepoCreateRe private void validateCreateRequest(GithubRepoCreateRequest request) { UrlUtil.validateGitHubUrl(request.url()); if (request.emails().isEmpty()) { - throw new GeneralException(GithubRepoException.NOT_FOUND_GITHUBEMAIL); + throw new GeneralException(GithubRepoException.CONTRIBUTOR_EMAIL_NOT_FOUND); } } diff --git a/src/main/java/com/specialwarriors/conal/notification/service/NotificationService.java b/src/main/java/com/specialwarriors/conal/notification/service/NotificationService.java index 1e52198..c477cc6 100644 --- a/src/main/java/com/specialwarriors/conal/notification/service/NotificationService.java +++ b/src/main/java/com/specialwarriors/conal/notification/service/NotificationService.java @@ -25,7 +25,7 @@ public class NotificationService { public void updateNotificationAgreement(long userId, long repositoryId, NotificationAgreementUpdateRequest request) { - GithubRepo githubRepo = githubRepoQuery.findByRepositoryId(repositoryId); + GithubRepo githubRepo = githubRepoQuery.findById(repositoryId); NotificationType notificationType = NotificationType.valueOf(request.type()); NotificationAgreement notificationAgreement = notificationAgreementQuery @@ -33,8 +33,9 @@ public void updateNotificationAgreement(long userId, long repositoryId, // 사용자가 자신의 github repo에 접근한 것이 맞는 지 검증 User user = userQuery.findById(userId); - if (!user.hasGithubRepo(repositoryId)) { - throw new GeneralException(GithubRepoException.UNAUTHORIZED_GITHUBREPO_ACCESS); + + if (user.notHasGithubRepo(githubRepo)) { + throw new GeneralException(GithubRepoException.UNAUTHORIZED_GITHUB_REPO_ACCESS); } if (request.isAgree()) { diff --git a/src/main/java/com/specialwarriors/conal/user/domain/User.java b/src/main/java/com/specialwarriors/conal/user/domain/User.java index 75b2c80..c189455 100644 --- a/src/main/java/com/specialwarriors/conal/user/domain/User.java +++ b/src/main/java/com/specialwarriors/conal/user/domain/User.java @@ -41,9 +41,9 @@ public User(int githubId, String username, String avatarUrl) { this.avatarUrl = avatarUrl; } - public boolean hasGithubRepo(long repositoryId) { + public boolean notHasGithubRepo(GithubRepo githubRepo) { - return githubRepos.stream().anyMatch(repo -> repo.getId() == repositoryId); + return !githubRepos.contains(githubRepo); } public void addGithubRepo(GithubRepo githubRepo) { diff --git a/src/main/java/com/specialwarriors/conal/vote/service/VoteService.java b/src/main/java/com/specialwarriors/conal/vote/service/VoteService.java index 6f1d0b0..583ee16 100644 --- a/src/main/java/com/specialwarriors/conal/vote/service/VoteService.java +++ b/src/main/java/com/specialwarriors/conal/vote/service/VoteService.java @@ -38,7 +38,7 @@ public void openVote(long repoId) { final Date issuedAt = new Date(); final long expirationMillis = 604800000; - GithubRepo githubRepo = githubRepoQuery.findByRepositoryId(repoId); + GithubRepo githubRepo = githubRepoQuery.findById(repoId); List contributors = githubRepo.getContributors(); String[] userTokens = contributors.stream().map(Contributor::getEmail) @@ -62,7 +62,7 @@ public List findVoteTargetEmails(long repoId, String userToken) { throw new GeneralException(VoteException.UNAUTHORIZED_VOTE_ACCESS); } - GithubRepo githubRepo = githubRepoQuery.findByRepositoryId(repoId); + GithubRepo githubRepo = githubRepoQuery.findById(repoId); return githubRepo.getContributors().stream() .map(Contributor::getEmail) diff --git a/src/test/java/com/specialwarriors/conal/github_repo/service/GithubRepoQueryTest.java b/src/test/java/com/specialwarriors/conal/github_repo/service/GithubRepoQueryTest.java new file mode 100644 index 0000000..bd0a460 --- /dev/null +++ b/src/test/java/com/specialwarriors/conal/github_repo/service/GithubRepoQueryTest.java @@ -0,0 +1,77 @@ +package com.specialwarriors.conal.github_repo.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.specialwarriors.conal.common.config.QuerydslConfig; +import com.specialwarriors.conal.common.exception.GeneralException; +import com.specialwarriors.conal.github_repo.domain.GithubRepo; +import com.specialwarriors.conal.github_repo.exception.GithubRepoException; +import com.specialwarriors.conal.github_repo.repository.GithubRepoRepository; +import com.specialwarriors.conal.user.repository.UserRepository; +import com.specialwarriors.conal.user.service.UserQuery; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.jdbc.Sql.ExecutionPhase; + +@ActiveProfiles("test") +@DataJpaTest +@Import(QuerydslConfig.class) +class GithubRepoQueryTest { + + @Autowired + private UserRepository userRepository; + + @Autowired + private GithubRepoRepository githubRepoRepository; + + private GithubRepoQuery githubRepoQuery; + + @BeforeEach + void init() { + UserQuery userQuery = new UserQuery(userRepository); + githubRepoQuery = new GithubRepoQuery(githubRepoRepository, userQuery); + } + + @Nested + @DisplayName("ID로 Github Repository를 조회할 때 ") + @Sql(scripts = "/sql/github_repo/service/find_by_repository_id_test_setup.sql", + executionPhase = ExecutionPhase.BEFORE_TEST_CLASS) + class FindByIdTest { + + @DisplayName("성공한다.") + @Test + public void success() { + // given + long repositoryId = 1L; + + // when + GithubRepo githubRepo = githubRepoQuery.findById(repositoryId); + + // then + assertThat(githubRepo.getId()).isEqualTo(repositoryId); + } + + @DisplayName("존재하지 않는 Repository일 경우 예외가 발생한다.") + @Test + public void githubRepoNotFound() { + // given + long repositoryId = 2L; + + // when + + // then + assertThatThrownBy(() -> githubRepoQuery.findById(repositoryId)) + .isInstanceOf(GeneralException.class) + .extracting("exception") + .isEqualTo(GithubRepoException.GITHUB_REPO_NOT_FOUND); + } + } +} diff --git a/src/test/resources/sql/github_repo/service/find_by_repository_id_test_setup.sql b/src/test/resources/sql/github_repo/service/find_by_repository_id_test_setup.sql new file mode 100644 index 0000000..6f648f4 --- /dev/null +++ b/src/test/resources/sql/github_repo/service/find_by_repository_id_test_setup.sql @@ -0,0 +1,5 @@ +insert into users(id, github_id, username, avatar_url) +values (1, 1, 'username', 'avatar_url'); + +insert into github_repos(id, name, url, end_date, user_id) +values (1, 'repo', 'repo_url', now(), 1); \ No newline at end of file