diff --git a/backend/src/main/java/reviewme/member/domain/GithubId.java b/backend/src/main/java/reviewme/member/domain/GithubId.java index be9d8ad88..c15a245c2 100644 --- a/backend/src/main/java/reviewme/member/domain/GithubId.java +++ b/backend/src/main/java/reviewme/member/domain/GithubId.java @@ -3,11 +3,13 @@ import jakarta.persistence.Column; import jakarta.persistence.Embeddable; import lombok.AccessLevel; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; @Embeddable @NoArgsConstructor(access = AccessLevel.PROTECTED) +@EqualsAndHashCode(of = "id") @Getter public class GithubId { diff --git a/backend/src/main/java/reviewme/member/domain/GithubIdReviewerGroup.java b/backend/src/main/java/reviewme/member/domain/GithubIdReviewerGroup.java index 47724ad24..5497a76b2 100644 --- a/backend/src/main/java/reviewme/member/domain/GithubIdReviewerGroup.java +++ b/backend/src/main/java/reviewme/member/domain/GithubIdReviewerGroup.java @@ -7,11 +7,13 @@ import jakarta.persistence.Id; import jakarta.persistence.ManyToOne; import lombok.AccessLevel; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) +@EqualsAndHashCode(of = "id") @Getter public class GithubIdReviewerGroup { diff --git a/backend/src/main/java/reviewme/member/domain/Member.java b/backend/src/main/java/reviewme/member/domain/Member.java index 2612e6366..0e142fb2d 100644 --- a/backend/src/main/java/reviewme/member/domain/Member.java +++ b/backend/src/main/java/reviewme/member/domain/Member.java @@ -7,14 +7,13 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; +import java.util.Objects; import lombok.AccessLevel; -import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; @Entity @Table(name = "member") -@EqualsAndHashCode(of = "id") @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter public class Member { @@ -33,4 +32,26 @@ public Member(String name, long githubId) { this.name = name; this.githubId = new GithubId(githubId); } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Member member)) { + return false; + } + if (id == null) { + return Objects.equals(githubId, member.githubId); + } + return Objects.equals(id, member.id); + } + + @Override + public int hashCode() { + if (id == null) { + return Objects.hash(githubId); + } + return Objects.hash(id); + } } diff --git a/backend/src/main/java/reviewme/member/domain/ReviewerGroup.java b/backend/src/main/java/reviewme/member/domain/ReviewerGroup.java index 3faa0aef6..60207d593 100644 --- a/backend/src/main/java/reviewme/member/domain/ReviewerGroup.java +++ b/backend/src/main/java/reviewme/member/domain/ReviewerGroup.java @@ -17,7 +17,7 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; -import reviewme.member.domain.exception.DescriptionLengthExceededException; +import reviewme.member.domain.exception.InvalidDescriptionLengthException; import reviewme.member.domain.exception.InvalidGroupNameLengthException; import reviewme.member.domain.exception.SelfReviewException; import reviewme.review.domain.Review; @@ -68,7 +68,7 @@ public ReviewerGroup(Member reviewee, List reviewerGithubIds, throw new InvalidGroupNameLengthException(MAX_GROUP_NAME_LENGTH); } if (description.length() > MAX_DESCRIPTION_LENGTH) { - throw new DescriptionLengthExceededException(MAX_DESCRIPTION_LENGTH); + throw new InvalidDescriptionLengthException(MAX_DESCRIPTION_LENGTH); } if (reviewerGithubIds.contains(reviewee.getGithubId())) { throw new SelfReviewException(); diff --git a/backend/src/main/java/reviewme/member/domain/exception/DescriptionLengthExceededException.java b/backend/src/main/java/reviewme/member/domain/exception/InvalidDescriptionLengthException.java similarity index 60% rename from backend/src/main/java/reviewme/member/domain/exception/DescriptionLengthExceededException.java rename to backend/src/main/java/reviewme/member/domain/exception/InvalidDescriptionLengthException.java index 701a073b2..35a19cd1f 100644 --- a/backend/src/main/java/reviewme/member/domain/exception/DescriptionLengthExceededException.java +++ b/backend/src/main/java/reviewme/member/domain/exception/InvalidDescriptionLengthException.java @@ -2,9 +2,9 @@ import reviewme.global.exception.BadRequestException; -public class DescriptionLengthExceededException extends BadRequestException { +public class InvalidDescriptionLengthException extends BadRequestException { - public DescriptionLengthExceededException(int maxLength) { + public InvalidDescriptionLengthException(int maxLength) { super("리뷰어 그룹 설명은 %d자 이하로 작성해야 합니다.".formatted(maxLength)); } } diff --git a/backend/src/test/java/reviewme/fixture/MemberFixture.java b/backend/src/test/java/reviewme/fixture/MemberFixture.java index 8959a9616..b278ee8ce 100644 --- a/backend/src/test/java/reviewme/fixture/MemberFixture.java +++ b/backend/src/test/java/reviewme/fixture/MemberFixture.java @@ -10,6 +10,8 @@ public enum MemberFixture { 회원_산초("산초", 1L), 회원_아루("아루", 2L), + 회원_커비("커비", 3L), + 회원_테드("테드", 4L), ; private final String name; diff --git a/backend/src/test/java/reviewme/fixture/ReviewerGroupFixture.java b/backend/src/test/java/reviewme/fixture/ReviewerGroupFixture.java new file mode 100644 index 000000000..ff4661051 --- /dev/null +++ b/backend/src/test/java/reviewme/fixture/ReviewerGroupFixture.java @@ -0,0 +1,26 @@ +package reviewme.fixture; + +import java.time.LocalDateTime; +import java.util.List; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import reviewme.member.domain.GithubId; +import reviewme.member.domain.Member; +import reviewme.member.domain.ReviewerGroup; + +@RequiredArgsConstructor +@Getter +public enum ReviewerGroupFixture { + + 데드라인_남은_그룹("데드라인 이전 그룹", "설명", LocalDateTime.now().plusDays(1)), + 데드라인_지난_그룹("데드라인 지난 그룹", "설명", LocalDateTime.now().minusDays(1)), + ; + + private final String groupName; + private final String description; + private final LocalDateTime deadline; + + public ReviewerGroup create(Member reviewee, List reviewerGithubIds) { + return new ReviewerGroup(reviewee, reviewerGithubIds, groupName, description, deadline); + } +} diff --git a/backend/src/test/java/reviewme/member/domain/ReviewerGroupTest.java b/backend/src/test/java/reviewme/member/domain/ReviewerGroupTest.java index 935727916..006e39cf5 100644 --- a/backend/src/test/java/reviewme/member/domain/ReviewerGroupTest.java +++ b/backend/src/test/java/reviewme/member/domain/ReviewerGroupTest.java @@ -2,49 +2,122 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static reviewme.fixture.MemberFixture.회원_산초; +import static reviewme.fixture.MemberFixture.회원_커비; import java.time.LocalDateTime; import java.util.List; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import reviewme.member.domain.exception.DescriptionLengthExceededException; +import reviewme.member.domain.exception.DuplicateReviewerException; +import reviewme.member.domain.exception.EmptyReviewerException; +import reviewme.member.domain.exception.InvalidDescriptionLengthException; import reviewme.member.domain.exception.InvalidGroupNameLengthException; +import reviewme.member.domain.exception.SelfReviewException; class ReviewerGroupTest { @Test void 리뷰_그룹이_올바르게_생성된다() { // given - Member sancho = new Member("산초", 1); + Member reviewee = 회원_산초.create(); String groupName = "a".repeat(100); String description = "a".repeat(50); - LocalDateTime createdAt = LocalDateTime.of(2024, 7, 17, 12, 0); + LocalDateTime deadline = LocalDateTime.now().plusDays(1); + List reviewerGithubIds = List.of(new GithubId(3)); // when, then - assertDoesNotThrow(() -> new ReviewerGroup(sancho, List.of(new GithubId(3)), groupName, description, createdAt)); + assertDoesNotThrow( + () -> new ReviewerGroup(reviewee, reviewerGithubIds, groupName, description, deadline)); } @ParameterizedTest @ValueSource(ints = {0, 101}) - void 리뷰_그룹_이름_길이_제한을_벗어나는_경우_예외를_발생한다(int length) { + void 리뷰_그룹_이름_길이_제한을_벗어날_수_없다(int length) { // given String groupName = "a".repeat(length); - Member sancho = new Member("산초", 1); - LocalDateTime createdAt = LocalDateTime.of(2024, 7, 17, 12, 0); + Member reviewee = 회원_산초.create(); + Member reviewer = 회원_커비.create(); + LocalDateTime deadline = LocalDateTime.now().plusDays(1); + List reviewerGithubIds = List.of(reviewer.getGithubId()); + // when, then - assertThatThrownBy(() -> new ReviewerGroup(sancho, List.of(), groupName, "설명", createdAt)) + assertThatThrownBy(() -> new ReviewerGroup(reviewee, reviewerGithubIds, groupName, "설명", deadline)) .isInstanceOf(InvalidGroupNameLengthException.class); } @Test - void 리뷰_그룹_설명_길이_제한을_벗어나는_경우_예외를_발생한다() { + void 리뷰_그룹_설명_길이_제한을_벗어날_수_없다() { // given String description = "a".repeat(51); - Member sancho = new Member("산초", 1); - LocalDateTime createdAt = LocalDateTime.of(2024, 7, 17, 12, 0); + Member reviewee = 회원_산초.create(); + Member reviewer = 회원_커비.create(); + LocalDateTime deadline = LocalDateTime.now().plusDays(1); + List reviewerGithubIds = List.of(reviewer.getGithubId()); + + // when, then + assertThatThrownBy(() -> new ReviewerGroup(reviewee, reviewerGithubIds, "그룹 이름", description, deadline)) + .isInstanceOf(InvalidDescriptionLengthException.class); + } + + @Test + void 리뷰어_목록에_리뷰이가_들어갈_수_없다() { + // given + Member reviewee = 회원_산초.create(); + String groupName = "Group"; + String description = "Description"; + LocalDateTime deadline = LocalDateTime.now().plusDays(1); + List reviewerGithubIds = List.of(reviewee.getGithubId()); + + // when, then + assertThatThrownBy(() -> new ReviewerGroup(reviewee, reviewerGithubIds, groupName, description, deadline)) + .isInstanceOf(SelfReviewException.class); + } + + @Test + void 리뷰어_목록이_비어있을_수_없다() { + Member reviewee = 회원_산초.create(); + String groupName = "Group"; + String description = "Description"; + LocalDateTime deadline = LocalDateTime.now().plusDays(1); + List reviewerGithubIds = List.of(); + + // when, then + assertThatThrownBy(() -> new ReviewerGroup(reviewee, reviewerGithubIds, groupName, description, deadline)) + .isInstanceOf(EmptyReviewerException.class); + } + + @Test + void 리뷰어를_중복으로_가지게_그룹을_생성할_수_없다() { + // given + Member reviewer = 회원_산초.create(); + Member reviewee = 회원_커비.create(); + String groupName = "Group"; + String description = "Description"; + LocalDateTime deadline = LocalDateTime.now().plusDays(1); + List reviewerGithubIds = List.of(reviewer.getGithubId(), reviewer.getGithubId()); + + // when, then + assertThatThrownBy(() -> new ReviewerGroup(reviewee, reviewerGithubIds, groupName, description, deadline)) + .isInstanceOf(DuplicateReviewerException.class); + } + + @Test + void 리뷰어를_중복으로_추가할_수_없다() { + // given + Member reviewer = 회원_커비.create(); + Member reviewee = 회원_산초.create(); + + String groupName = "Group"; + String description = "Description"; + LocalDateTime deadline = LocalDateTime.now().plusDays(1); + List reviewerGithubIds = List.of(reviewer.getGithubId()); + ReviewerGroup reviewerGroup = new ReviewerGroup(reviewee, reviewerGithubIds, groupName, description, deadline); + GithubIdReviewerGroup githubIdReviewerGroup = new GithubIdReviewerGroup(reviewee.getGithubId(), reviewerGroup); + // when, then - assertThatThrownBy(() -> new ReviewerGroup(sancho, List.of(), "그룹 이름", description, createdAt)) - .isInstanceOf(DescriptionLengthExceededException.class); + assertThatThrownBy(() -> reviewerGroup.addReviewerGithubId(githubIdReviewerGroup)) + .isInstanceOf(DuplicateReviewerException.class); } } diff --git a/backend/src/test/java/reviewme/review/domain/ReviewTest.java b/backend/src/test/java/reviewme/review/domain/ReviewTest.java index 1be4bd759..22d356655 100644 --- a/backend/src/test/java/reviewme/review/domain/ReviewTest.java +++ b/backend/src/test/java/reviewme/review/domain/ReviewTest.java @@ -4,18 +4,28 @@ import static reviewme.fixture.KeywordFixture.꼼꼼하게_기록해요; import static reviewme.fixture.KeywordFixture.의견을_잘_조율해요; import static reviewme.fixture.MemberFixture.회원_산초; +import static reviewme.fixture.MemberFixture.회원_아루; +import static reviewme.fixture.MemberFixture.회원_커비; +import static reviewme.fixture.ReviewerGroupFixture.데드라인_남은_그룹; +import static reviewme.fixture.ReviewerGroupFixture.데드라인_지난_그룹; import java.time.LocalDateTime; import java.util.List; import org.junit.jupiter.api.Test; import reviewme.keyword.domain.Keyword; +import reviewme.member.domain.GithubId; import reviewme.member.domain.Member; +import reviewme.member.domain.ReviewerGroup; +import reviewme.review.domain.exception.DeadlineExpiredException; import reviewme.review.domain.exception.IllegalReviewerException; +import reviewme.review.domain.exception.RevieweeMismatchException; +import reviewme.review.exception.GithubReviewerGroupUnAuthorizedException; +import reviewme.review.exception.ReviewAlreadySubmittedException; class ReviewTest { @Test - void 리뷰어와_리뷰이가_다른_경우_예외를_발생한다() { + void 리뷰어와_리뷰이가_같을_수_없다() { // given Member member = 회원_산초.create(); LocalDateTime createdAt = LocalDateTime.now(); @@ -25,4 +35,62 @@ class ReviewTest { assertThatThrownBy(() -> new Review(member, member, null, keywords, createdAt)) .isInstanceOf(IllegalReviewerException.class); } + + @Test + void 마감_기한이_지난_그룹에_리뷰를_등록할_수_없다() { + // given + Member reviewer = 회원_산초.create(); + Member reviewee = 회원_아루.create(); + ReviewerGroup reviewerGroup = 데드라인_지난_그룹.create(reviewee, List.of(reviewer.getGithubId())); + LocalDateTime createdAt = LocalDateTime.now(); + List keywords = List.of(); + + // when, then + assertThatThrownBy(() -> new Review(reviewer, reviewee, reviewerGroup, keywords, createdAt)) + .isInstanceOf(DeadlineExpiredException.class); + } + + @Test + void 하나의_리뷰_그룹에_중복으로_리뷰를_등록할_수_없다() { + // given + Member reviewer = 회원_산초.create(); + Member reviewee = 회원_아루.create(); + ReviewerGroup reviewerGroup = 데드라인_남은_그룹.create(reviewee, List.of(reviewer.getGithubId())); + new Review(reviewer, reviewee, reviewerGroup, List.of(), LocalDateTime.now()); + LocalDateTime createdAt = LocalDateTime.now(); + List keywords = List.of(); + + // when, then + assertThatThrownBy(() -> new Review(reviewer, reviewee, reviewerGroup, keywords, createdAt)) + .isInstanceOf(ReviewAlreadySubmittedException.class); + } + + @Test + void 리뷰어로_등록되지_않은_회원은_리뷰를_등록할_수_없다() { + // given + Member reviewer = new Member("reviewer", 1); + Member reviewee = new Member("reviewee", 2); + ReviewerGroup reviewerGroup = 데드라인_남은_그룹.create(reviewee, List.of(new GithubId(3))); + LocalDateTime createdAt = LocalDateTime.now(); + List keywords = List.of(); + + // when, then + assertThatThrownBy(() -> new Review(reviewer, reviewee, reviewerGroup, keywords, createdAt)) + .isInstanceOf(GithubReviewerGroupUnAuthorizedException.class); + } + + @Test + void 그룹_내에서_그룹_밖으로_리뷰를_작성할_수_없다() { + // given + Member reviewer = 회원_산초.create(); + Member reviewee = 회원_아루.create(); + Member other = 회원_커비.create(); + ReviewerGroup reviewerGroup = 데드라인_남은_그룹.create(reviewee, List.of(reviewer.getGithubId())); + LocalDateTime createdAt = LocalDateTime.now(); + List keywords = List.of(); + + // when, then + assertThatThrownBy(() -> new Review(reviewer, other, reviewerGroup, keywords, createdAt)) + .isInstanceOf(RevieweeMismatchException.class); + } }