diff --git a/src/main/java/com/challenge/api/service/challenge/ChallengeService.java b/src/main/java/com/challenge/api/service/challenge/ChallengeService.java index 28eceed..c1647bc 100644 --- a/src/main/java/com/challenge/api/service/challenge/ChallengeService.java +++ b/src/main/java/com/challenge/api/service/challenge/ChallengeService.java @@ -14,9 +14,9 @@ import com.challenge.domain.challenge.Challenge; import com.challenge.domain.challenge.ChallengeQueryRepository; import com.challenge.domain.challenge.ChallengeRepository; +import com.challenge.domain.challengeRecord.ChallengeRecord; +import com.challenge.domain.challengeRecord.ChallengeRecordRepository; import com.challenge.domain.member.Member; -import com.challenge.domain.record.Record; -import com.challenge.domain.record.RecordRepository; import com.challenge.utils.date.DateUtils; import com.challenge.validator.DateValidator; import lombok.RequiredArgsConstructor; @@ -36,7 +36,7 @@ public class ChallengeService { private final ChallengeRepository challengeRepository; private final CategoryRepository categoryRepository; - private final RecordRepository recordRepository; + private final ChallengeRecordRepository challengeRecordRepository; private final ChallengeValidator challengeValidator; private final CategoryValidator categoryValidator; @@ -75,16 +75,13 @@ public ChallengeResponse createChallenge(Member member, ChallengeCreateServiceRe public ChallengeResponse achieveChallenge(Member member, Long challengeId, ChallengeAchieveServiceRequest request) { // validation - challengeValidator.challengeExistsBy(member, challengeId); - DateValidator.isLocalDateFormatter(request.getAchieveDate()); - DateValidator.isBeforeOrEqualToTodayFrom(request.getAchieveDate()); + validateChallengeAchieveOrCancel(member, challengeId, request.getAchieveDate()); Challenge challenge = challengeRepository.getReferenceById(challengeId); challengeValidator.hasDuplicateRecordFor(challenge, DateUtils.toLocalDate(request.getAchieveDate())); - Record record = Record.achieve(challenge, request.getAchieveDate()); - recordRepository.save(record); - challenge.addRecord(record); + ChallengeRecord challengeRecord = ChallengeRecord.achieve(challenge, request.getAchieveDate()); + challengeRecordRepository.save(challengeRecord); return ChallengeResponse.of(challenge); } @@ -92,19 +89,18 @@ public ChallengeResponse achieveChallenge(Member member, Long challengeId, @Transactional public ChallengeResponse cancelChallenge(Member member, Long challengeId, ChallengeCancelServiceRequest request) { // validation - challengeValidator.challengeExistsBy(member, challengeId); - DateValidator.isLocalDateFormatter(request.getCancelDate()); - DateValidator.isBeforeOrEqualToTodayFrom(request.getCancelDate()); + validateChallengeAchieveOrCancel(member, challengeId, request.getCancelDate()); Challenge challenge = challengeRepository.getReferenceById(challengeId); // validation - Record record = recordValidator.hasRecordFor(challenge, DateUtils.toLocalDate(request.getCancelDate())); + ChallengeRecord challengeRecord = recordValidator.isLatestRecordSuccessfulBy(challenge, + DateUtils.toLocalDate(request.getCancelDate())); // 기록 삭제 - challenge.getRecords().remove(record); -// Record removedRecord = record.cancel(challenge, request.getCancelDate()); -// recordRepository.save(removedRecord); + challenge.getChallengeRecords().remove(challengeRecord); + ChallengeRecord cancelRecord = ChallengeRecord.cancel(challenge, request.getCancelDate()); + challengeRecordRepository.save(cancelRecord); return ChallengeResponse.of(challenge); } @@ -132,4 +128,11 @@ public Long deleteChallenge(Member member, Long challengeId) { return challengeId; } + // 챌린지 달성 또는 취소 시 유효성 검사 + private void validateChallengeAchieveOrCancel(Member member, Long challengeId, String actionDate) { + challengeValidator.challengeExistsBy(member, challengeId); + DateValidator.isLocalDateFormatter(actionDate); + DateValidator.isBeforeOrEqualToTodayFrom(actionDate); + } + } diff --git a/src/main/java/com/challenge/api/service/record/response/RecordResponse.java b/src/main/java/com/challenge/api/service/record/response/RecordResponse.java index ef06859..930221e 100644 --- a/src/main/java/com/challenge/api/service/record/response/RecordResponse.java +++ b/src/main/java/com/challenge/api/service/record/response/RecordResponse.java @@ -1,7 +1,7 @@ package com.challenge.api.service.record.response; import com.challenge.domain.challenge.Challenge; -import com.challenge.domain.record.Record; +import com.challenge.domain.challengeRecord.ChallengeRecord; import com.challenge.utils.date.DateUtils; import lombok.Builder; import lombok.Getter; @@ -19,15 +19,16 @@ private RecordResponse(List successDates) { } public static RecordResponse of(Challenge challenge) { - List records = challenge.getRecords(); - if (records.isEmpty()) { + List challengeRecords = challenge.getChallengeRecords(); + if (challengeRecords.isEmpty()) { return null; } return RecordResponse.builder() .successDates( - records.stream() - .map(record -> DateUtils.toDayString(record.getSuccessDate())) + challengeRecords.stream() + .filter(ChallengeRecord::isSucceed) + .map(record -> DateUtils.toDayString(record.getRecordDate())) .toList() ) .build(); diff --git a/src/main/java/com/challenge/api/validator/RecordValidator.java b/src/main/java/com/challenge/api/validator/RecordValidator.java index 938c2a9..b1bedf9 100644 --- a/src/main/java/com/challenge/api/validator/RecordValidator.java +++ b/src/main/java/com/challenge/api/validator/RecordValidator.java @@ -1,8 +1,8 @@ package com.challenge.api.validator; import com.challenge.domain.challenge.Challenge; -import com.challenge.domain.record.Record; -import com.challenge.domain.record.RecordRepository; +import com.challenge.domain.challengeRecord.ChallengeRecord; +import com.challenge.domain.challengeRecord.ChallengeRecordQueryRepository; import com.challenge.exception.ErrorCode; import com.challenge.exception.GlobalException; import lombok.RequiredArgsConstructor; @@ -16,21 +16,17 @@ @RequiredArgsConstructor public class RecordValidator { - private final RecordRepository recordRepository; - - /** - * 특정 챌린지와 날짜에 해당하는 레코드가 존재하는지 검증하고 반환 - * - * @param challenge 검증 대상 챌린지 - * @param cancelDate 취소하려는 날짜 - * @return 검증된 Record 엔티티 - * @throws GlobalException RECORD_NOT_FOUND 예외 - */ - public Record hasRecordFor(Challenge challenge, LocalDate cancelDate) { - return challenge.getRecords().stream() - .filter(r -> r.getSuccessDate().equals(cancelDate)) - .findFirst() - .orElseThrow(() -> new GlobalException(ErrorCode.RECORD_NOT_FOUND)); + private final ChallengeRecordQueryRepository challengeRecordQueryRepository; + + public ChallengeRecord isLatestRecordSuccessfulBy(Challenge challenge, LocalDate cancelDate) { + ChallengeRecord challengeRecord = challengeRecordQueryRepository.isLatestRecordSuccessfulBy(challenge, + cancelDate); + + if (challengeRecord != null) { + return challengeRecord; + } + + throw new GlobalException(ErrorCode.LATEST_ACHIEVE_RECORD_NOT_FOUND); } } diff --git a/src/main/java/com/challenge/domain/challenge/Challenge.java b/src/main/java/com/challenge/domain/challenge/Challenge.java index 047bb3c..401fb10 100644 --- a/src/main/java/com/challenge/domain/challenge/Challenge.java +++ b/src/main/java/com/challenge/domain/challenge/Challenge.java @@ -4,8 +4,8 @@ import com.challenge.api.service.challenge.request.ChallengeUpdateServiceRequest; import com.challenge.domain.BaseDateTimeEntity; import com.challenge.domain.category.Category; +import com.challenge.domain.challengeRecord.ChallengeRecord; import com.challenge.domain.member.Member; -import com.challenge.domain.record.Record; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -36,7 +36,7 @@ public class Challenge extends BaseDateTimeEntity { private Long id; @OneToMany(mappedBy = "challenge", cascade = CascadeType.REMOVE, orphanRemoval = true) - private List records = new ArrayList<>(); + private List challengeRecords = new ArrayList<>(); @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "member_id", nullable = false) @@ -65,7 +65,7 @@ public class Challenge extends BaseDateTimeEntity { private String color; @Column(nullable = false) - private boolean isDeleted; + private boolean isDeleted = false; @Column(nullable = false) private LocalDateTime startDateTime; @@ -75,8 +75,8 @@ public class Challenge extends BaseDateTimeEntity { @Builder private Challenge(Member member, Category category, String title, String content, int durationInWeeks, - int weeklyGoalCount, String color, LocalDateTime startDateTime) { - this.records = new ArrayList<>(); + int weeklyGoalCount, String color, boolean isDeleted, LocalDateTime startDateTime) { + this.challengeRecords = new ArrayList<>(); this.member = member; this.category = category; this.title = title; @@ -85,7 +85,7 @@ private Challenge(Member member, Category category, String title, String content this.weeklyGoalCount = weeklyGoalCount; this.totalGoalCount = durationInWeeks * weeklyGoalCount; this.color = color; - this.isDeleted = false; + this.isDeleted = isDeleted; this.startDateTime = startDateTime; this.endDateTime = startDateTime.plusWeeks(durationInWeeks) .toLocalDate() @@ -115,8 +115,8 @@ public void update(Category category, ChallengeUpdateServiceRequest request) { this.content = request.getContent(); } - public void addRecord(Record record) { - this.records.add(record); + public void addRecord(ChallengeRecord challengeRecord) { + this.challengeRecords.add(challengeRecord); } public void delete() { diff --git a/src/main/java/com/challenge/domain/challenge/ChallengeQueryRepository.java b/src/main/java/com/challenge/domain/challenge/ChallengeQueryRepository.java index e46ce22..618a5d8 100644 --- a/src/main/java/com/challenge/domain/challenge/ChallengeQueryRepository.java +++ b/src/main/java/com/challenge/domain/challenge/ChallengeQueryRepository.java @@ -2,7 +2,6 @@ import com.challenge.domain.member.Member; import com.challenge.utils.date.DateUtils; -import com.querydsl.core.types.dsl.Expressions; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; @@ -12,7 +11,7 @@ import java.util.List; import static com.challenge.domain.challenge.QChallenge.challenge; -import static com.challenge.domain.record.QRecord.record; +import static com.challenge.domain.challengeRecord.QChallengeRecord.challengeRecord; @RequiredArgsConstructor @Repository @@ -35,10 +34,11 @@ public List findChallengesBy(Member member, LocalDate targetDate) { // 중복된 기록이 존재하는지 조회 public boolean existsDuplicateRecordBy(Challenge challenge, LocalDate successDate) { - Long count = queryFactory.select(record.count()) - .from(record) - .where(record.challenge.eq(challenge) - .and(record.successDate.eq(successDate))) + Long count = queryFactory.select(challengeRecord.count()) + .from(challengeRecord) + .where(challengeRecord.challenge.eq(challenge) + .and(challengeRecord.recordDate.eq(successDate)) + .and(challengeRecord.isSucceed.eq(true))) .fetchOne(); return count != null && count > 0; @@ -70,17 +70,7 @@ public Long countOngoingChallengesBy(Member member) { // 완료된 챌린지 수 조회 public Long countCompletedChallengesBy(Member member) { - return queryFactory - .select(challenge.count()) - .from(challenge) - .leftJoin(record) - .on(record.challenge.eq(challenge) - .and(record.isSucceed.isTrue()) - ) - .where(challenge.member.eq(member)) - .groupBy(challenge) - .having(record.count().eq(Expressions.constant(challenge.totalGoalCount))) - .fetchOne(); + return null; } // 전체 챌린지 수 조회 diff --git a/src/main/java/com/challenge/domain/challenge/ChallengeStatus.java b/src/main/java/com/challenge/domain/challenge/ChallengeStatus.java new file mode 100644 index 0000000..e6a9242 --- /dev/null +++ b/src/main/java/com/challenge/domain/challenge/ChallengeStatus.java @@ -0,0 +1,22 @@ +package com.challenge.domain.challenge; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public enum ChallengeStatus { + + // - Q. 챌린지 기간이 만료되는 경우, 어떻게 자동으로 챌린지의 상태를 변경할 수 있을까? + // A. 스케줄러 라이브러리를 사용하여 매 00시 00분 00초에 챌린지의 상태를 확인하여 챌린지 기간이 만료된 경우 상태를 변경한다. + + // - Q. 챌린지의 상태를 변경하는 로직은 어디에 위치하는 것이 좋을까? + // A. 챌린지의 상태를 변경하는 로직은 챌린지 도메인에 위치하는 것이 좋다. + + ONGOING("진행 중"), + SUCCEED("성공"), // 모든 목표를 달성한 경우 + FAILED("실패"), // 하나라도 목표를 달성하지 못한 경우 + REMOVED("삭제"); + + private final String description; +} diff --git a/src/main/java/com/challenge/domain/record/Record.java b/src/main/java/com/challenge/domain/challengeRecord/ChallengeRecord.java similarity index 60% rename from src/main/java/com/challenge/domain/record/Record.java rename to src/main/java/com/challenge/domain/challengeRecord/ChallengeRecord.java index 5f4e5d0..e6c5512 100644 --- a/src/main/java/com/challenge/domain/record/Record.java +++ b/src/main/java/com/challenge/domain/challengeRecord/ChallengeRecord.java @@ -1,4 +1,4 @@ -package com.challenge.domain.record; +package com.challenge.domain.challengeRecord; import com.challenge.domain.BaseDateTimeEntity; import com.challenge.domain.challenge.Challenge; @@ -21,43 +21,43 @@ @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class Record extends BaseDateTimeEntity { +public class ChallengeRecord extends BaseDateTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "record_id") + @Column(name = "challenge_record_id") private Long id; - @Column(nullable = false) - private LocalDate successDate; - - @Column(nullable = false) - private boolean isSucceed = false; - @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "challenge_id", nullable = false) private Challenge challenge; + @Column(nullable = false) + private LocalDate recordDate; + + @Column(nullable = false) + private boolean isSucceed; + @Builder - private Record(LocalDate successDate, boolean isSucceed, Challenge challenge) { - this.successDate = successDate; + private ChallengeRecord(LocalDate recordDate, boolean isSucceed, Challenge challenge) { + this.recordDate = recordDate; this.challenge = challenge; this.isSucceed = isSucceed; } - public static Record achieve(Challenge challenge, String achieveDate) { - Record record = Record.builder() - .successDate(DateUtils.toLocalDate(achieveDate)) + public static ChallengeRecord achieve(Challenge challenge, String achieveDate) { + ChallengeRecord achieveRecord = ChallengeRecord.builder() + .recordDate(DateUtils.toLocalDate(achieveDate)) .isSucceed(true) .challenge(challenge) .build(); - challenge.addRecord(record); - return record; + challenge.addRecord(achieveRecord); + return achieveRecord; } - public Record cancel(Challenge challenge, String cancelDate) { - return Record.builder() - .successDate(DateUtils.toLocalDate(cancelDate)) + public static ChallengeRecord cancel(Challenge challenge, String cancelDate) { + return ChallengeRecord.builder() + .recordDate(DateUtils.toLocalDate(cancelDate)) .isSucceed(false) .challenge(challenge) .build(); diff --git a/src/main/java/com/challenge/domain/challengeRecord/ChallengeRecordQueryRepository.java b/src/main/java/com/challenge/domain/challengeRecord/ChallengeRecordQueryRepository.java new file mode 100644 index 0000000..70b7662 --- /dev/null +++ b/src/main/java/com/challenge/domain/challengeRecord/ChallengeRecordQueryRepository.java @@ -0,0 +1,35 @@ +package com.challenge.domain.challengeRecord; + +import com.challenge.domain.challenge.Challenge; +import com.challenge.exception.ErrorCode; +import com.challenge.exception.GlobalException; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.time.LocalDate; + +import static com.challenge.domain.challengeRecord.QChallengeRecord.challengeRecord; + +@RequiredArgsConstructor +@Repository +public class ChallengeRecordQueryRepository { + + private final JPAQueryFactory queryFactory; + + public ChallengeRecord isLatestRecordSuccessfulBy(Challenge challenge, LocalDate cancelDate) { + ChallengeRecord latestRecord = queryFactory.selectFrom(challengeRecord) + .where(challengeRecord.challenge.eq(challenge) + .and(challengeRecord.recordDate.eq(cancelDate)) + ) + .orderBy(challengeRecord.createdAt.desc()) + .fetchFirst(); + + if (latestRecord != null && latestRecord.isSucceed()) { + return latestRecord; + } + + throw new GlobalException(ErrorCode.LATEST_ACHIEVE_RECORD_NOT_FOUND); + } + +} diff --git a/src/main/java/com/challenge/domain/challengeRecord/ChallengeRecordRepository.java b/src/main/java/com/challenge/domain/challengeRecord/ChallengeRecordRepository.java new file mode 100644 index 0000000..853f707 --- /dev/null +++ b/src/main/java/com/challenge/domain/challengeRecord/ChallengeRecordRepository.java @@ -0,0 +1,7 @@ +package com.challenge.domain.challengeRecord; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ChallengeRecordRepository extends JpaRepository { + +} diff --git a/src/main/java/com/challenge/domain/record/RecordRepository.java b/src/main/java/com/challenge/domain/record/RecordRepository.java deleted file mode 100644 index 305b2bf..0000000 --- a/src/main/java/com/challenge/domain/record/RecordRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.challenge.domain.record; - -import org.springframework.data.jpa.repository.JpaRepository; - -public interface RecordRepository extends JpaRepository { - -} diff --git a/src/main/java/com/challenge/exception/ErrorCode.java b/src/main/java/com/challenge/exception/ErrorCode.java index 0e9aa48..89b94b6 100644 --- a/src/main/java/com/challenge/exception/ErrorCode.java +++ b/src/main/java/com/challenge/exception/ErrorCode.java @@ -71,7 +71,7 @@ public enum ErrorCode { * 기록 관련 에러 */ DUPLICATE_RECORD(BAD_REQUEST, "RECORD_4001", "오늘 이미 해당 챌린지를 달성했습니다."), - RECORD_NOT_FOUND(NOT_FOUND, "RECORD_4002", "기록 정보를 찾을 수 없습니다. 관리자에게 문의 바랍니다."), + LATEST_ACHIEVE_RECORD_NOT_FOUND(NOT_FOUND, "RECORD_4002", "최근 달성한 기록을 찾을 수 없습니다."), /** * 날짜 관련 에러 @@ -90,5 +90,6 @@ public enum ErrorCode { private final HttpStatus status; private final String code; private final String message; + } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 316a18d..52c112f 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -37,7 +37,7 @@ spring: jpa: hibernate: - ddl-auto: update + ddl-auto: none show-sql: true properties: hibernate: diff --git a/src/test/java/com/challenge/api/service/challenge/ChallengeServiceTest.java b/src/test/java/com/challenge/api/service/challenge/ChallengeServiceTest.java index f2ce81a..1267f65 100644 --- a/src/test/java/com/challenge/api/service/challenge/ChallengeServiceTest.java +++ b/src/test/java/com/challenge/api/service/challenge/ChallengeServiceTest.java @@ -11,6 +11,8 @@ import com.challenge.domain.category.CategoryRepository; import com.challenge.domain.challenge.Challenge; import com.challenge.domain.challenge.ChallengeRepository; +import com.challenge.domain.challengeRecord.ChallengeRecord; +import com.challenge.domain.challengeRecord.ChallengeRecordRepository; import com.challenge.domain.job.Job; import com.challenge.domain.job.JobRepository; import com.challenge.domain.member.Gender; @@ -18,8 +20,6 @@ import com.challenge.domain.member.LoginType; import com.challenge.domain.member.Member; import com.challenge.domain.member.MemberRepository; -import com.challenge.domain.record.Record; -import com.challenge.domain.record.RecordRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -51,14 +51,14 @@ class ChallengeServiceTest { private ChallengeRepository challengeRepository; @Autowired - private RecordRepository recordRepository; + private ChallengeRecordRepository challengeRecordRepository; @Autowired private ChallengeService challengeService; @AfterEach void tearDown() { - recordRepository.deleteAllInBatch(); + challengeRecordRepository.deleteAllInBatch(); challengeRepository.deleteAllInBatch(); memberRepository.deleteAllInBatch(); jobRepository.deleteAllInBatch(); @@ -160,10 +160,10 @@ void achieveChallenge() { LocalDateTime.of(2024, 11, 11, 10, 10, 30)); challengeRepository.save(challenge); - Record record1 = createRecord(challenge, LocalDate.of(2024, 11, 11)); - Record record2 = createRecord(challenge, LocalDate.of(2024, 11, 12)); - Record record3 = createRecord(challenge, LocalDate.of(2024, 11, 13)); - recordRepository.saveAll(List.of(record1, record2, record3)); + ChallengeRecord challengeRecord1 = createRecord(challenge, LocalDate.of(2024, 11, 11)); + ChallengeRecord challengeRecord2 = createRecord(challenge, LocalDate.of(2024, 11, 12)); + ChallengeRecord challengeRecord3 = createRecord(challenge, LocalDate.of(2024, 11, 13)); + challengeRecordRepository.saveAll(List.of(challengeRecord1, challengeRecord2, challengeRecord3)); ChallengeAchieveServiceRequest challengeAchieveServiceRequest = ChallengeAchieveServiceRequest.builder() .achieveDate("2024-11-14") @@ -431,10 +431,11 @@ private Category createCategory(String category) { .build(); } - private Record createRecord(Challenge challenge, LocalDate currentDate) { - return Record.builder() + private ChallengeRecord createRecord(Challenge challenge, LocalDate currentDate) { + return ChallengeRecord.builder() .challenge(challenge) - .successDate(currentDate) + .recordDate(currentDate) + .isSucceed(true) .build(); } diff --git a/src/test/java/com/challenge/api/validator/ChallengeValidatorTest.java b/src/test/java/com/challenge/api/validator/ChallengeValidatorTest.java index f069a9b..29f31a3 100644 --- a/src/test/java/com/challenge/api/validator/ChallengeValidatorTest.java +++ b/src/test/java/com/challenge/api/validator/ChallengeValidatorTest.java @@ -5,6 +5,8 @@ import com.challenge.domain.category.CategoryRepository; import com.challenge.domain.challenge.Challenge; import com.challenge.domain.challenge.ChallengeRepository; +import com.challenge.domain.challengeRecord.ChallengeRecord; +import com.challenge.domain.challengeRecord.ChallengeRecordRepository; import com.challenge.domain.job.Job; import com.challenge.domain.job.JobRepository; import com.challenge.domain.member.Gender; @@ -12,8 +14,6 @@ import com.challenge.domain.member.LoginType; import com.challenge.domain.member.Member; import com.challenge.domain.member.MemberRepository; -import com.challenge.domain.record.Record; -import com.challenge.domain.record.RecordRepository; import com.challenge.exception.GlobalException; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; @@ -47,11 +47,11 @@ class ChallengeValidatorTest { private JobRepository jobRepository; @Autowired - private RecordRepository recordRepository; + private ChallengeRecordRepository challengeRecordRepository; @AfterEach void tearDown() { - recordRepository.deleteAllInBatch(); + challengeRecordRepository.deleteAllInBatch(); challengeRepository.deleteAllInBatch(); memberRepository.deleteAllInBatch(); categoryRepository.deleteAllInBatch(); @@ -90,8 +90,8 @@ void duplicateRecord() { Challenge challenge = Challenge.create(member, category, request, LocalDateTime.now()); challengeRepository.save(challenge); - Record record = createRecord(challenge, currentDate); - recordRepository.save(record); + ChallengeRecord challengeRecord = createRecord(challenge, currentDate); + challengeRecordRepository.save(challengeRecord); // when // then assertThatThrownBy(() -> challengeValidator.hasDuplicateRecordFor(challenge, currentDate)) @@ -124,10 +124,11 @@ private Category createCategory() { .build(); } - private Record createRecord(Challenge challenge, LocalDate currentDate) { - return Record.builder() + private ChallengeRecord createRecord(Challenge challenge, LocalDate currentDate) { + return ChallengeRecord.builder() .challenge(challenge) - .successDate(currentDate) + .recordDate(currentDate) + .isSucceed(true) .build(); } diff --git a/src/test/java/com/challenge/api/validator/RecordValidatorTest.java b/src/test/java/com/challenge/api/validator/RecordValidatorTest.java new file mode 100644 index 0000000..27e3540 --- /dev/null +++ b/src/test/java/com/challenge/api/validator/RecordValidatorTest.java @@ -0,0 +1,135 @@ +package com.challenge.api.validator; + +import com.challenge.domain.category.Category; +import com.challenge.domain.category.CategoryRepository; +import com.challenge.domain.challenge.Challenge; +import com.challenge.domain.challenge.ChallengeRepository; +import com.challenge.domain.challengeRecord.ChallengeRecord; +import com.challenge.domain.challengeRecord.ChallengeRecordRepository; +import com.challenge.domain.job.Job; +import com.challenge.domain.job.JobRepository; +import com.challenge.domain.member.Gender; +import com.challenge.domain.member.JobYear; +import com.challenge.domain.member.LoginType; +import com.challenge.domain.member.Member; +import com.challenge.domain.member.MemberRepository; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@ActiveProfiles("test") +class RecordValidatorTest { + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private CategoryRepository categoryRepository; + + @Autowired + private JobRepository jobRepository; + + @Autowired + private ChallengeRecordRepository challengeRecordRepository; + + @Autowired + private ChallengeRepository challengeRepository; + + @Autowired + private RecordValidator recordValidator; + + @AfterEach + void tearDown() { + challengeRecordRepository.deleteAllInBatch(); + challengeRepository.deleteAllInBatch(); + memberRepository.deleteAllInBatch(); + jobRepository.deleteAllInBatch(); + categoryRepository.deleteAllInBatch(); + } + + @DisplayName("특정 날짜의 최신 기록이 성공인지 확인한다.") + @Test + void isLatestRecordSuccessful() { + // given + Member member = createMember(); + memberRepository.save(member); + + Category category = createCategory(); + categoryRepository.save(category); + + Challenge challenge = createChallenge(member, category, LocalDateTime.of(2024, 10, 1, 12, 30, 59)); + challengeRepository.save(challenge); + + ChallengeRecord record1 = ChallengeRecord.builder() + .challenge(challenge) + .recordDate(LocalDate.of(2024, 10, 3)) + .isSucceed(true) + .build(); + ChallengeRecord record2 = ChallengeRecord.builder() + .challenge(challenge) + .recordDate(LocalDate.of(2024, 10, 3)) + .isSucceed(false) + .build(); + ChallengeRecord record3 = ChallengeRecord.builder() + .challenge(challenge) + .recordDate(LocalDate.of(2024, 10, 3)) + .isSucceed(true) + .build(); + challengeRecordRepository.saveAll(List.of(record1, record2, record3)); + + // when + ChallengeRecord latestRecord = recordValidator.isLatestRecordSuccessfulBy(challenge, LocalDate.of(2024, 10, 3)); + + // then + assertThat(latestRecord.getId()).isNotNull(); + assertThat(latestRecord.isSucceed()).isTrue(); + } + + private Member createMember() { + Job job = Job.builder() + .code("1") + .description("1") + .build(); + jobRepository.save(job); + + return Member.builder() + .socialId(1L) + .email("eamil") + .loginType(LoginType.KAKAO) + .nickname("닉네임") + .birth(LocalDate.of(2000, 1, 1)) + .gender(Gender.MALE) + .jobYear(JobYear.LT_1Y) + .job(job) + .build(); + } + + private Category createCategory() { + return Category.builder() + .name("카테고리") + .build(); + } + + private Challenge createChallenge(Member member, Category category, LocalDateTime startDateTime) { + return Challenge.builder() + .member(member) + .category(category) + .title("제목") + .color("#30B0C7") + .durationInWeeks(1) + .weeklyGoalCount(1) + .startDateTime(startDateTime) + .build(); + } + +} diff --git a/src/test/java/com/challenge/domain/challenge/ChallengeQueryRepositoryTest.java b/src/test/java/com/challenge/domain/challenge/ChallengeQueryRepositoryTest.java index de83b04..9b3eeeb 100644 --- a/src/test/java/com/challenge/domain/challenge/ChallengeQueryRepositoryTest.java +++ b/src/test/java/com/challenge/domain/challenge/ChallengeQueryRepositoryTest.java @@ -3,6 +3,8 @@ import com.challenge.api.service.challenge.request.ChallengeCreateServiceRequest; import com.challenge.domain.category.Category; import com.challenge.domain.category.CategoryRepository; +import com.challenge.domain.challengeRecord.ChallengeRecord; +import com.challenge.domain.challengeRecord.ChallengeRecordRepository; import com.challenge.domain.job.Job; import com.challenge.domain.job.JobRepository; import com.challenge.domain.member.Gender; @@ -10,8 +12,6 @@ import com.challenge.domain.member.LoginType; import com.challenge.domain.member.Member; import com.challenge.domain.member.MemberRepository; -import com.challenge.domain.record.Record; -import com.challenge.domain.record.RecordRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -46,11 +46,11 @@ class ChallengeQueryRepositoryTest { private JobRepository jobRepository; @Autowired - private RecordRepository recordRepository; + private ChallengeRecordRepository challengeRecordRepository; @AfterEach void tearDown() { - recordRepository.deleteAllInBatch(); + challengeRecordRepository.deleteAllInBatch(); challengeRepository.deleteAllInBatch(); memberRepository.deleteAllInBatch(); categoryRepository.deleteAllInBatch(); @@ -69,11 +69,11 @@ void findChallengesBy() { Member member = createMember(); memberRepository.save(member); - Challenge challenge1 = createChallenge(member, category, 1, "제목1", + Challenge challenge1 = createChallenge(member, category, 1, "제목1", false, LocalDateTime.of(2024, 10, 1, 12, 30, 59)); - Challenge challenge2 = createChallenge(member, category, 2, "제목2", + Challenge challenge2 = createChallenge(member, category, 2, "제목2", false, LocalDateTime.of(2024, 11, 16, 14, 0, 0)); - Challenge challenge3 = createChallenge(member, category, 3, "제목3", + Challenge challenge3 = createChallenge(member, category, 3, "제목3", false, LocalDateTime.of(2024, 12, 1, 0, 0, 0)); challengeRepository.saveAll(List.of(challenge1, challenge2, challenge3)); @@ -91,7 +91,7 @@ void findChallengesBy() { @DisplayName("입력 받은 날짜 기준 2달 전 날짜의 00:00:00부터 입력받은 날짜의 23:59:59까지의 시간 범위를 계산 한다.") @Test - void shouldCalculateStartAndEndDateTimeForChallengeQuery() throws Exception { + void shouldCalculateStartAndEndDateTimeForChallengeQuery() { // given LocalDate localDate = LocalDate.of(2024, 11, 11); @@ -121,8 +121,8 @@ void existsDuplicateRecordBy() { Challenge challenge = Challenge.create(member, category, request, LocalDateTime.now()); challengeRepository.save(challenge); - Record record = createRecord(challenge, currentDate); - recordRepository.save(record); + ChallengeRecord challengeRecord = createRecord(challenge, currentDate); + challengeRecordRepository.save(challengeRecord); // when boolean result = challengeQueryRepository.existsDuplicateRecordBy(challenge, currentDate); @@ -133,7 +133,7 @@ void existsDuplicateRecordBy() { @DisplayName("진행중인 챌린지 수를 조회한다.") @Test - void countOngoingChallengesBy() { + void countOngoingChallenges() { // given String targetDateTime = "2025-01-08 12:30:59"; @@ -143,13 +143,13 @@ void countOngoingChallengesBy() { Category category = createCategory(); categoryRepository.save(category); - Challenge challenge1 = createChallenge(member, category, 1, "제목1", + Challenge challenge1 = createChallenge(member, category, 1, "제목1", false, LocalDateTime.of(2024, 10, 1, 12, 30, 59)); - Challenge challenge2 = createChallenge(member, category, 2, "제목2", + Challenge challenge2 = createChallenge(member, category, 2, "제목2", false, LocalDateTime.of(2024, 11, 11, 14, 0, 0)); - Challenge challenge3 = createChallenge(member, category, 3, "제목3", + Challenge challenge3 = createChallenge(member, category, 3, "제목3", false, LocalDateTime.of(2024, 12, 23, 0, 0, 0)); - Challenge challenge4 = createChallenge(member, category, 1, "제목4", + Challenge challenge4 = createChallenge(member, category, 1, "제목4", false, LocalDateTime.of(2025, 1, 1, 0, 0, 0)); challengeRepository.saveAll(List.of(challenge1, challenge2, challenge3, challenge4)); @@ -162,7 +162,34 @@ void countOngoingChallengesBy() { @DisplayName("완료된 챌린지 수를 조회한다.") @Test - void countCompletedChallengesBy() { + void countCompletedChallenges() { + } + + @DisplayName("전체 챌린지 수를 조회한다.") + @Test + void countAllChallenges() { + // given + Member member = createMember(); + memberRepository.save(member); + + Category category = createCategory(); + categoryRepository.save(category); + + Challenge challenge1 = createChallenge(member, category, 1, "제목1", false, + LocalDateTime.of(2024, 10, 1, 12, 30, 59)); + Challenge challenge2 = createChallenge(member, category, 2, "제목2", false, + LocalDateTime.of(2024, 11, 11, 14, 0, 0)); + Challenge challenge3 = createChallenge(member, category, 3, "제목3", true, + LocalDateTime.of(2024, 12, 23, 0, 0, 0)); + Challenge challenge4 = createChallenge(member, category, 1, "제목4", true, + LocalDateTime.of(2025, 1, 1, 0, 0, 0)); + challengeRepository.saveAll(List.of(challenge1, challenge2, challenge3, challenge4)); + + // when + Long count = challengeQueryRepository.countAllChallengesBy(member); + + // then + assertThat(count).isEqualTo(2); } private Member createMember() { @@ -185,13 +212,14 @@ private Member createMember() { } private Challenge createChallenge(Member member, Category category, int durationInWeeks, String title, - LocalDateTime startDateTime) { + boolean isDeleted, LocalDateTime startDateTime) { return Challenge.builder() .member(member) .category(category) .durationInWeeks(durationInWeeks) .title(title) .color("#30B0C7") + .isDeleted(isDeleted) .weeklyGoalCount(1) .startDateTime(startDateTime) .build(); @@ -203,10 +231,11 @@ private Category createCategory() { .build(); } - private Record createRecord(Challenge challenge, LocalDate currentDate) { - return Record.builder() + private ChallengeRecord createRecord(Challenge challenge, LocalDate currentDate) { + return ChallengeRecord.builder() .challenge(challenge) - .successDate(currentDate) + .recordDate(currentDate) + .isSucceed(true) .build(); } diff --git a/src/test/java/com/challenge/domain/challenge/ChallengeTest.java b/src/test/java/com/challenge/domain/challenge/ChallengeTest.java index b549ae3..c30f32b 100644 --- a/src/test/java/com/challenge/domain/challenge/ChallengeTest.java +++ b/src/test/java/com/challenge/domain/challenge/ChallengeTest.java @@ -52,7 +52,7 @@ void recordInit() { Challenge challenge = Challenge.create(member, category, request, LocalDateTime.now()); // then - assertThat(challenge.getRecords()).isEmpty(); + assertThat(challenge.getChallengeRecords()).isEmpty(); } @DisplayName("챌린지 생성 시 총 목표 횟수는 기간 * 목표 횟수로 계산한다. ") diff --git a/src/test/java/com/challenge/domain/challengeRecord/ChallengeRecordQueryRepositoryTest.java b/src/test/java/com/challenge/domain/challengeRecord/ChallengeRecordQueryRepositoryTest.java new file mode 100644 index 0000000..d2accde --- /dev/null +++ b/src/test/java/com/challenge/domain/challengeRecord/ChallengeRecordQueryRepositoryTest.java @@ -0,0 +1,170 @@ +package com.challenge.domain.challengeRecord; + +import com.challenge.domain.category.Category; +import com.challenge.domain.category.CategoryRepository; +import com.challenge.domain.challenge.Challenge; +import com.challenge.domain.challenge.ChallengeRepository; +import com.challenge.domain.job.Job; +import com.challenge.domain.job.JobRepository; +import com.challenge.domain.member.Gender; +import com.challenge.domain.member.JobYear; +import com.challenge.domain.member.LoginType; +import com.challenge.domain.member.Member; +import com.challenge.domain.member.MemberRepository; +import com.challenge.exception.GlobalException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@SpringBootTest +@ActiveProfiles("test") +class ChallengeRecordQueryRepositoryTest { + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private CategoryRepository categoryRepository; + + @Autowired + private JobRepository jobRepository; + + @Autowired + private ChallengeRepository challengeRepository; + + @Autowired + private ChallengeRecordRepository challengeRecordRepository; + + @Autowired + private ChallengeRecordQueryRepository challengeRecordQueryRepository; + + @AfterEach + void tearDown() { + challengeRecordRepository.deleteAllInBatch(); + challengeRepository.deleteAllInBatch(); + memberRepository.deleteAllInBatch(); + jobRepository.deleteAllInBatch(); + categoryRepository.deleteAllInBatch(); + } + + @DisplayName("특정 날짜의 가장 최근 기록이 성공 기록이 아니면 예외를 발생시킨다.") + @Test + void isLatestRecordSuccessful_Fail() { + // given + Member member = createMember(); + memberRepository.save(member); + + Category category = createCategory(); + categoryRepository.save(category); + + Challenge challenge = createChallenge(member, category, LocalDateTime.of(2024, 10, 1, 12, 30, 59)); + challengeRepository.save(challenge); + + ChallengeRecord record1 = ChallengeRecord.builder() + .challenge(challenge) + .recordDate(LocalDate.of(2024, 10, 3)) + .isSucceed(true) + .build(); + ChallengeRecord record2 = ChallengeRecord.builder() + .challenge(challenge) + .recordDate(LocalDate.of(2024, 10, 3)) + .isSucceed(false) + .build(); + challengeRecordRepository.saveAll(List.of(record1, record2)); + + LocalDate cancelDate = LocalDate.of(2024, 10, 3); + // when // then + assertThatThrownBy(() -> challengeRecordQueryRepository.isLatestRecordSuccessfulBy(challenge, cancelDate)) + .isInstanceOf(GlobalException.class) + .hasMessage("최근 달성한 기록을 찾을 수 없습니다."); + } + + @DisplayName("특정 날짜의 가장 최근 기록이 성공 기록이면 해당 기록을 반환한다.") + @Test + void isLatestRecordSuccessful() { + // given + Member member = createMember(); + memberRepository.save(member); + + Category category = createCategory(); + categoryRepository.save(category); + + Challenge challenge = createChallenge(member, category, LocalDateTime.of(2024, 10, 1, 12, 30, 59)); + challengeRepository.save(challenge); + + ChallengeRecord record1 = ChallengeRecord.builder() + .challenge(challenge) + .recordDate(LocalDate.of(2024, 10, 3)) + .isSucceed(true) + .build(); + ChallengeRecord record2 = ChallengeRecord.builder() + .challenge(challenge) + .recordDate(LocalDate.of(2024, 10, 3)) + .isSucceed(false) + .build(); + ChallengeRecord record3 = ChallengeRecord.builder() + .challenge(challenge) + .recordDate(LocalDate.of(2024, 10, 3)) + .isSucceed(true) + .build(); + challengeRecordRepository.saveAll(List.of(record1, record2, record3)); + + // when + ChallengeRecord latestRecord = challengeRecordQueryRepository.isLatestRecordSuccessfulBy( + challenge, + LocalDate.of(2024, 10, 3) + ); + + // then + assertThat(latestRecord.getId()).isNotNull(); + assertThat(latestRecord.isSucceed()).isTrue(); + } + + private Member createMember() { + Job job = Job.builder() + .code("1") + .description("1") + .build(); + jobRepository.save(job); + + return Member.builder() + .socialId(1L) + .email("eamil") + .loginType(LoginType.KAKAO) + .nickname("닉네임") + .birth(LocalDate.of(2000, 1, 1)) + .gender(Gender.MALE) + .jobYear(JobYear.LT_1Y) + .job(job) + .build(); + } + + private Category createCategory() { + return Category.builder() + .name("카테고리") + .build(); + } + + private Challenge createChallenge(Member member, Category category, LocalDateTime startDateTime) { + return Challenge.builder() + .member(member) + .category(category) + .title("제목") + .color("#30B0C7") + .durationInWeeks(1) + .weeklyGoalCount(1) + .startDateTime(startDateTime) + .build(); + } + +}