From 5626eb0f46f9d4f48d53037493a48e9e99965cdc Mon Sep 17 00:00:00 2001 From: sungHeeLee <70899677+hee9841@users.noreply.github.com> Date: Thu, 31 Oct 2024 19:56:36 +0900 Subject: [PATCH] =?UTF-8?q?Fix:=20=EB=82=98=EC=9D=98=20=EB=B1=83=EC=A7=80?= =?UTF-8?q?=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20=EC=9D=91=EB=8B=B5?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD=20=EC=82=AC=ED=95=AD=EC=97=90=20=EB=A7=9E?= =?UTF-8?q?=EA=B2=8C=20=EC=88=98=EC=A0=95=20(#292)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Refactor: AchievedBadgesResponse의 AchievedBadge를 공통으로 사용하기 위해 분리 * Feat: 사용자가 획득한 뱃지를 뱃지 타입순, 최신 획득 순으로 가져오는 repository 추가 * Fix: 뱃지 목록 조회의 새로운 응답 형식 변경에 따른 수정 - AllBadgesListResponse변경 - 기존의 뱃지 목록을 조회하는 repository 삭제 - 필요 없어진 BadgeWithAchieveStatusAndAchievedAt DTO 삭제 - 기존 테스트 코드 삭제 - BagdeType에 타입한글 이름 추가 * Fix: 최신 획득 뱃지는 badgeList에 추가하지 않고 따로 리턴하게 수정 * Test: findByMemberIdOrderByBadgeTypeAndAchievedAt 리파지토리 테스트 * Test: 나의 벳지 목록 조회 서비스 테스트 코드 추가 * Fix: 보여주는 우선순위 값 추가 * Fix: 벳지의 타입 별 보여주는 우선 순위로 배지 리스트 반환하게 수정 * Test: findBadgesList의 잘못된 테스트 코드 수정 * Docs: 나의 뱃지 목록 조회 API 문서 수정 * Fix: Collection 자료형 타입을 interface 타입으로 사용 * Fix: responseBadges 리스트에 forEach로 추가하는 대신, 스트림 map을 이용해 리스트로 반환 --- .../runus/application/badge/BadgeService.java | 57 ++++++---- .../badge/BadgeAchievementRepository.java | 2 + .../runus/domain/badge/BadgeRepository.java | 2 - .../BadgeWithAchieveStatusAndAchievedAt.java | 12 --- .../dnd/runus/global/constant/BadgeType.java | 20 +++- .../badge/BadgeAchievementRepositoryImpl.java | 5 + .../domain/badge/BadgeRepositoryImpl.java | 8 -- .../badge/JooqBadgeAchievementRepository.java | 14 +++ .../jooq/badge/JooqBadgeRepository.java | 45 -------- .../v1/badge/BadgeController.java | 4 +- .../v1/badge/dto/response/AchievedBadge.java | 5 + .../dto/response/AchievedBadgesResponse.java | 8 -- .../dto/response/AllBadgesListResponse.java | 39 ++----- .../application/badge/BadgeServiceTest.java | 100 +++++------------- .../BadgeAchievementRepositoryImplTest.java | 28 ++++- .../domain/badge/BadgeRepositoryTest.java | 84 --------------- 16 files changed, 142 insertions(+), 291 deletions(-) delete mode 100644 src/main/java/com/dnd/runus/domain/badge/BadgeWithAchieveStatusAndAchievedAt.java delete mode 100644 src/main/java/com/dnd/runus/infrastructure/persistence/jooq/badge/JooqBadgeRepository.java create mode 100644 src/main/java/com/dnd/runus/presentation/v1/badge/dto/response/AchievedBadge.java delete mode 100644 src/test/java/com/dnd/runus/infrastructure/persistence/domain/badge/BadgeRepositoryTest.java diff --git a/src/main/java/com/dnd/runus/application/badge/BadgeService.java b/src/main/java/com/dnd/runus/application/badge/BadgeService.java index d8a81e66..ea183209 100644 --- a/src/main/java/com/dnd/runus/application/badge/BadgeService.java +++ b/src/main/java/com/dnd/runus/application/badge/BadgeService.java @@ -3,9 +3,10 @@ import com.dnd.runus.domain.badge.*; import com.dnd.runus.domain.member.Member; import com.dnd.runus.global.constant.BadgeType; +import com.dnd.runus.presentation.v1.badge.dto.response.AchievedBadge; import com.dnd.runus.presentation.v1.badge.dto.response.AchievedBadgesResponse; import com.dnd.runus.presentation.v1.badge.dto.response.AllBadgesListResponse; -import com.dnd.runus.presentation.v1.badge.dto.response.AllBadgesListResponse.BadgeWithAchievedStatus; +import com.dnd.runus.presentation.v1.badge.dto.response.AllBadgesListResponse.BadgesWithType; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -13,8 +14,13 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.util.Collections; +import java.util.Comparator; import java.util.EnumMap; +import java.util.EnumSet; import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; import java.util.stream.Collectors; import static com.dnd.runus.global.constant.TimeConstant.SERVER_TIMEZONE_ID; @@ -41,7 +47,7 @@ public void achieveBadge(Member member, BadgeType badgeType, int value) { public AchievedBadgesResponse getAchievedBadges(long memberId) { return new AchievedBadgesResponse( badgeAchievementRepository.findByMemberIdWithBadgeOrderByAchievedAtLimit(memberId, 3).stream() - .map(badgeAchievement -> new AchievedBadgesResponse.AchievedBadge( + .map(badgeAchievement -> new AchievedBadge( badgeAchievement.badge().badgeId(), badgeAchievement.badge().name(), badgeAchievement.badge().imageUrl(), @@ -52,36 +58,47 @@ public AchievedBadgesResponse getAchievedBadges(long memberId) { @Transactional(readOnly = true) public AllBadgesListResponse getListOfAllBadges(long memberId) { - List allBadges = - badgeRepository.findAllBadgesWithAchieveStatusByMemberId(memberId); + List allBadges = + badgeAchievementRepository.findByMemberIdOrderByBadgeTypeAndAchievedAt(memberId); + // 최신 획득한 뱃지(오늘 기준으로 일주일) LocalDateTime oneWeekAgo = LocalDate.now(SERVER_TIMEZONE_ID) .atStartOfDay(SERVER_TIMEZONE_ID) .toOffsetDateTime() .minusDays(7) .toLocalDateTime(); - EnumMap> badgeMap = allBadges.stream() + List recencyBadges = allBadges.stream() + .filter(v -> oneWeekAgo.isBefore(v.createdAt().toLocalDateTime())) + .map(v -> new AchievedBadge( + v.badge().badgeId(), + v.badge().name(), + v.badge().imageUrl(), + v.createdAt().toLocalDateTime())) + .toList(); + + // badgeType별 획득한 배지 + Map> badgesWithType = allBadges.stream() .collect(Collectors.groupingBy( v -> v.badge().type(), () -> new EnumMap<>(BadgeType.class), - Collectors.mapping(BadgeWithAchievedStatus::from, Collectors.toList()))); + Collectors.mapping( + v -> new AchievedBadge( + v.badge().badgeId(), + v.badge().name(), + v.badge().imageUrl(), + v.createdAt().toLocalDateTime()), + Collectors.toList()))); - List recencyBadges = allBadges.stream() - .filter(badge -> isRecent(badge, oneWeekAgo)) - .map(BadgeWithAchievedStatus::from) - .toList(); + // 타입 별, showPriority순서로 리스트에 추가 + Set badgeTypesSet = new TreeSet<>(Comparator.comparingInt(BadgeType::getShowPriority)); + badgeTypesSet.addAll(EnumSet.allOf(BadgeType.class)); - return new AllBadgesListResponse( - recencyBadges, - badgeMap.getOrDefault(BadgeType.PERSONAL_RECORD, Collections.emptyList()), - badgeMap.getOrDefault(BadgeType.DISTANCE_METER, Collections.emptyList()), - badgeMap.getOrDefault(BadgeType.STREAK, Collections.emptyList()), - badgeMap.getOrDefault(BadgeType.DURATION_SECONDS, Collections.emptyList()), - badgeMap.getOrDefault(BadgeType.LEVEL, Collections.emptyList())); - } + List responseBadges = badgeTypesSet.stream() + .map(badgeType -> new BadgesWithType( + badgeType.getName(), badgesWithType.getOrDefault(badgeType, Collections.emptyList()))) + .toList(); - private boolean isRecent(BadgeWithAchieveStatusAndAchievedAt badge, LocalDateTime criterionDate) { - return badge.isAchieved() && criterionDate.isBefore(badge.achievedAt()); + return new AllBadgesListResponse(recencyBadges, responseBadges); } } diff --git a/src/main/java/com/dnd/runus/domain/badge/BadgeAchievementRepository.java b/src/main/java/com/dnd/runus/domain/badge/BadgeAchievementRepository.java index ee738d17..e3536ad5 100644 --- a/src/main/java/com/dnd/runus/domain/badge/BadgeAchievementRepository.java +++ b/src/main/java/com/dnd/runus/domain/badge/BadgeAchievementRepository.java @@ -9,6 +9,8 @@ public interface BadgeAchievementRepository { List findByMemberIdWithBadgeOrderByAchievedAtLimit(long memberId, int limit); + List findByMemberIdOrderByBadgeTypeAndAchievedAt(long memberId); + BadgeAchievement save(BadgeAchievement badgeAchievement); void saveAllIgnoreDuplicated(List badgeAchievements); diff --git a/src/main/java/com/dnd/runus/domain/badge/BadgeRepository.java b/src/main/java/com/dnd/runus/domain/badge/BadgeRepository.java index 975a15e3..504d7cf1 100644 --- a/src/main/java/com/dnd/runus/domain/badge/BadgeRepository.java +++ b/src/main/java/com/dnd/runus/domain/badge/BadgeRepository.java @@ -6,6 +6,4 @@ public interface BadgeRepository { List findByTypeAndRequiredValueLessThanEqual(BadgeType badgeType, int requiredValue); - - List findAllBadgesWithAchieveStatusByMemberId(long memberId); } diff --git a/src/main/java/com/dnd/runus/domain/badge/BadgeWithAchieveStatusAndAchievedAt.java b/src/main/java/com/dnd/runus/domain/badge/BadgeWithAchieveStatusAndAchievedAt.java deleted file mode 100644 index 78cd4d4d..00000000 --- a/src/main/java/com/dnd/runus/domain/badge/BadgeWithAchieveStatusAndAchievedAt.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.dnd.runus.domain.badge; - -import jakarta.validation.constraints.NotNull; - -import java.time.LocalDateTime; -import java.time.OffsetDateTime; - -public record BadgeWithAchieveStatusAndAchievedAt(@NotNull Badge badge, boolean isAchieved, LocalDateTime achievedAt) { - public BadgeWithAchieveStatusAndAchievedAt(Badge badge, OffsetDateTime achievedAt) { - this(badge, achievedAt != null, achievedAt != null ? achievedAt.toLocalDateTime() : null); - } -} diff --git a/src/main/java/com/dnd/runus/global/constant/BadgeType.java b/src/main/java/com/dnd/runus/global/constant/BadgeType.java index 76f654ad..a624b807 100644 --- a/src/main/java/com/dnd/runus/global/constant/BadgeType.java +++ b/src/main/java/com/dnd/runus/global/constant/BadgeType.java @@ -1,9 +1,19 @@ package com.dnd.runus.global.constant; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import static lombok.AccessLevel.PRIVATE; + +@Getter +@RequiredArgsConstructor(access = PRIVATE) public enum BadgeType { - STREAK, - DISTANCE_METER, - PERSONAL_RECORD, - DURATION_SECONDS, - LEVEL, + PERSONAL_RECORD("개인기록", 1), + DISTANCE_METER("러닝거리", 2), + STREAK("연속", 3), + DURATION_SECONDS("시간", 4), + LEVEL("레벨", 5), + ; + private final String name; + private final int showPriority; } diff --git a/src/main/java/com/dnd/runus/infrastructure/persistence/domain/badge/BadgeAchievementRepositoryImpl.java b/src/main/java/com/dnd/runus/infrastructure/persistence/domain/badge/BadgeAchievementRepositoryImpl.java index 91805710..79c9febe 100644 --- a/src/main/java/com/dnd/runus/infrastructure/persistence/domain/badge/BadgeAchievementRepositoryImpl.java +++ b/src/main/java/com/dnd/runus/infrastructure/persistence/domain/badge/BadgeAchievementRepositoryImpl.java @@ -28,6 +28,11 @@ public List findByMemberIdWithBadgeOrderByAchievedAt return jooqBadgeAchievementRepository.findByMemberIdWithBadgeOrderByAchievedAtLimit(memberId, limit); } + @Override + public List findByMemberIdOrderByBadgeTypeAndAchievedAt(long memberId) { + return jooqBadgeAchievementRepository.findByMemberIdOrderByBadgeTypeAndAchievedAt(memberId); + } + @Override public BadgeAchievement save(BadgeAchievement badgeAchievement) { return jpaBadgeAchievementRepository diff --git a/src/main/java/com/dnd/runus/infrastructure/persistence/domain/badge/BadgeRepositoryImpl.java b/src/main/java/com/dnd/runus/infrastructure/persistence/domain/badge/BadgeRepositoryImpl.java index e8751e95..8f198c92 100644 --- a/src/main/java/com/dnd/runus/infrastructure/persistence/domain/badge/BadgeRepositoryImpl.java +++ b/src/main/java/com/dnd/runus/infrastructure/persistence/domain/badge/BadgeRepositoryImpl.java @@ -2,9 +2,7 @@ import com.dnd.runus.domain.badge.Badge; import com.dnd.runus.domain.badge.BadgeRepository; -import com.dnd.runus.domain.badge.BadgeWithAchieveStatusAndAchievedAt; import com.dnd.runus.global.constant.BadgeType; -import com.dnd.runus.infrastructure.persistence.jooq.badge.JooqBadgeRepository; import com.dnd.runus.infrastructure.persistence.jpa.badge.JpaBadgeRepository; import com.dnd.runus.infrastructure.persistence.jpa.badge.entity.BadgeEntity; import lombok.RequiredArgsConstructor; @@ -16,7 +14,6 @@ @RequiredArgsConstructor public class BadgeRepositoryImpl implements BadgeRepository { private final JpaBadgeRepository jpaBadgeRepository; - private final JooqBadgeRepository jooqBadgeRepository; @Override public List findByTypeAndRequiredValueLessThanEqual(BadgeType badgeType, int requiredValue) { @@ -24,9 +21,4 @@ public List findByTypeAndRequiredValueLessThanEqual(BadgeType badgeType, .map(BadgeEntity::toDomain) .toList(); } - - @Override - public List findAllBadgesWithAchieveStatusByMemberId(long memberId) { - return jooqBadgeRepository.findAllBadgesWithAchieveStatusByMemberId(memberId); - } } diff --git a/src/main/java/com/dnd/runus/infrastructure/persistence/jooq/badge/JooqBadgeAchievementRepository.java b/src/main/java/com/dnd/runus/infrastructure/persistence/jooq/badge/JooqBadgeAchievementRepository.java index f45a468e..c831e263 100644 --- a/src/main/java/com/dnd/runus/infrastructure/persistence/jooq/badge/JooqBadgeAchievementRepository.java +++ b/src/main/java/com/dnd/runus/infrastructure/persistence/jooq/badge/JooqBadgeAchievementRepository.java @@ -31,6 +31,20 @@ public List findByMemberIdWithBadgeOrderByAchievedAt badge.get(BADGE_ACHIEVEMENT.UPDATED_AT, OffsetDateTime.class))); } + public List findByMemberIdOrderByBadgeTypeAndAchievedAt(long memberId) { + return dsl.select() + .from(BADGE_ACHIEVEMENT) + .join(BADGE) + .on(BADGE_ACHIEVEMENT.BADGE_ID.eq(BADGE.ID)) + .where(BADGE_ACHIEVEMENT.MEMBER_ID.eq(memberId)) + .orderBy(BADGE.TYPE, BADGE_ACHIEVEMENT.CREATED_AT.desc()) + .fetch(badge -> new BadgeAchievement.OnlyBadge( + badge.get(BADGE_ACHIEVEMENT.ID), + new JooqBadgeMapper().map(badge), + badge.get(BADGE_ACHIEVEMENT.CREATED_AT, OffsetDateTime.class), + badge.get(BADGE_ACHIEVEMENT.UPDATED_AT, OffsetDateTime.class))); + } + public void saveAllIgnoreDuplicated(List badgeAchievements) { OffsetDateTime now = OffsetDateTime.now(); dsl.batch(badgeAchievements.stream() diff --git a/src/main/java/com/dnd/runus/infrastructure/persistence/jooq/badge/JooqBadgeRepository.java b/src/main/java/com/dnd/runus/infrastructure/persistence/jooq/badge/JooqBadgeRepository.java deleted file mode 100644 index f54da4cd..00000000 --- a/src/main/java/com/dnd/runus/infrastructure/persistence/jooq/badge/JooqBadgeRepository.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.dnd.runus.infrastructure.persistence.jooq.badge; - -import com.dnd.runus.domain.badge.Badge; -import com.dnd.runus.domain.badge.BadgeWithAchieveStatusAndAchievedAt; -import com.dnd.runus.global.constant.BadgeType; -import lombok.RequiredArgsConstructor; -import org.jooq.DSLContext; -import org.jooq.Record; -import org.jooq.RecordMapper; -import org.springframework.stereotype.Repository; - -import java.time.OffsetDateTime; -import java.util.List; - -import static com.dnd.runus.jooq.Tables.BADGE; -import static com.dnd.runus.jooq.Tables.BADGE_ACHIEVEMENT; - -@Repository -@RequiredArgsConstructor -public class JooqBadgeRepository { - private final DSLContext dsl; - - public List findAllBadgesWithAchieveStatusByMemberId(long memberId) { - return dsl.select(BADGE.ID, BADGE.NAME, BADGE.IMAGE_URL, BADGE.TYPE, BADGE_ACHIEVEMENT.CREATED_AT) - .from(BADGE) - .leftJoin(BADGE_ACHIEVEMENT) - .on(BADGE.ID.eq(BADGE_ACHIEVEMENT.BADGE_ID)) - .and(BADGE_ACHIEVEMENT.MEMBER_ID.eq(memberId)) - .fetch(new AllBadgesMapper()); - } - - private static class AllBadgesMapper implements RecordMapper { - @Override - public BadgeWithAchieveStatusAndAchievedAt map(Record record) { - OffsetDateTime achievedTime = record.get(BADGE_ACHIEVEMENT.CREATED_AT); - return new BadgeWithAchieveStatusAndAchievedAt( - new Badge( - record.get(BADGE.ID), - record.get(BADGE.NAME), - record.get(BADGE.IMAGE_URL), - BadgeType.valueOf(record.get(BADGE.TYPE))), - achievedTime); - } - } -} diff --git a/src/main/java/com/dnd/runus/presentation/v1/badge/BadgeController.java b/src/main/java/com/dnd/runus/presentation/v1/badge/BadgeController.java index a9497f89..3c4329da 100644 --- a/src/main/java/com/dnd/runus/presentation/v1/badge/BadgeController.java +++ b/src/main/java/com/dnd/runus/presentation/v1/badge/BadgeController.java @@ -28,9 +28,9 @@ public AchievedBadgesResponse getMyBadges(@MemberId long memberId) { description = """ 서비스의 전체 뱃지를 조회 합니다.
- 자신이 획득한 뱃지는 isAchieved가 true로 표시됩니다.
- 뱃지의 카테고리별 리스트로 뱃지가 리턴됩니다.
최근 일주일에 사용자가 획득한 뱃지는 recencyBadges 리스트에 리턴됩니다.
+ 뱃지의 카테고리 별 리스트로 뱃지가 리턴됩니다.
+ 자세한 응답 구조는 AllBadgesListResponse의 Schema을 확인해주세요.
""") public AllBadgesListResponse getAllBadgesList(@MemberId long memberId) { return badgeService.getListOfAllBadges(memberId); diff --git a/src/main/java/com/dnd/runus/presentation/v1/badge/dto/response/AchievedBadge.java b/src/main/java/com/dnd/runus/presentation/v1/badge/dto/response/AchievedBadge.java new file mode 100644 index 00000000..5d83ede5 --- /dev/null +++ b/src/main/java/com/dnd/runus/presentation/v1/badge/dto/response/AchievedBadge.java @@ -0,0 +1,5 @@ +package com.dnd.runus.presentation.v1.badge.dto.response; + +import java.time.LocalDateTime; + +public record AchievedBadge(long badgeId, String name, String imageUrl, LocalDateTime achievedAt) {} diff --git a/src/main/java/com/dnd/runus/presentation/v1/badge/dto/response/AchievedBadgesResponse.java b/src/main/java/com/dnd/runus/presentation/v1/badge/dto/response/AchievedBadgesResponse.java index 4d97351a..323c8f76 100644 --- a/src/main/java/com/dnd/runus/presentation/v1/badge/dto/response/AchievedBadgesResponse.java +++ b/src/main/java/com/dnd/runus/presentation/v1/badge/dto/response/AchievedBadgesResponse.java @@ -1,16 +1,8 @@ package com.dnd.runus.presentation.v1.badge.dto.response; -import java.time.LocalDateTime; import java.util.List; public record AchievedBadgesResponse( List badges ) { - public record AchievedBadge( - long badgeId, - String name, - String imageUrl, - LocalDateTime achievedAt - ) { - } } diff --git a/src/main/java/com/dnd/runus/presentation/v1/badge/dto/response/AllBadgesListResponse.java b/src/main/java/com/dnd/runus/presentation/v1/badge/dto/response/AllBadgesListResponse.java index 58c6c9a2..18331c72 100644 --- a/src/main/java/com/dnd/runus/presentation/v1/badge/dto/response/AllBadgesListResponse.java +++ b/src/main/java/com/dnd/runus/presentation/v1/badge/dto/response/AllBadgesListResponse.java @@ -1,41 +1,18 @@ package com.dnd.runus.presentation.v1.badge.dto.response; -import com.dnd.runus.domain.badge.Badge; -import com.dnd.runus.domain.badge.BadgeWithAchieveStatusAndAchievedAt; import io.swagger.v3.oas.annotations.media.Schema; -import java.time.LocalDateTime; import java.util.List; public record AllBadgesListResponse( - @Schema(description = "신규 뱃지 목록") - List recencyBadges, - @Schema(description = "개인기록 뱃지 목록") - List personalBadges, - @Schema(description = "러닝 거리 뱃지 목록") - List distanceBadges, - @Schema(description = "연속 뱃지 목록") - List streakBadges, - @Schema(description = "사간 뱃지 목록") - List durationBadges, - @Schema(description = "레벨 뱃지 목록") - List levelBadges + @Schema(description = "최신 뱃지 목록(일주일 기준), 없으면 빈리스트 리턴") + List recencyBadges, + List badgesList ) { - public record BadgeWithAchievedStatus( - @Schema(description = "뱃지 id") - long badgeId, - @Schema(description = "뱃지 이름") - String name, - @Schema(description = "뱃지 이미지 url") - String imageUrl, - @Schema(description = "뱃지 달성 여부") - boolean isAchieved, - @Schema(description = "배지 달성 날짜") - LocalDateTime achievedAt + public record BadgesWithType( + @Schema(description = "뱃지 카테고리 이름") + String category, + @Schema(description = "뱃지 리스트") + List badges ) { - public static BadgeWithAchievedStatus from( - BadgeWithAchieveStatusAndAchievedAt badgeWithAchievedStatus) { - Badge badge = badgeWithAchievedStatus.badge(); - return new BadgeWithAchievedStatus(badge.badgeId(), badge.name(), badge.imageUrl(), badgeWithAchievedStatus.isAchieved(), badgeWithAchievedStatus.achievedAt()); - } } } diff --git a/src/test/java/com/dnd/runus/application/badge/BadgeServiceTest.java b/src/test/java/com/dnd/runus/application/badge/BadgeServiceTest.java index 41a4f16c..ddc362bf 100644 --- a/src/test/java/com/dnd/runus/application/badge/BadgeServiceTest.java +++ b/src/test/java/com/dnd/runus/application/badge/BadgeServiceTest.java @@ -4,11 +4,10 @@ import com.dnd.runus.domain.badge.BadgeAchievement; import com.dnd.runus.domain.badge.BadgeAchievementRepository; import com.dnd.runus.domain.badge.BadgeRepository; -import com.dnd.runus.domain.badge.BadgeWithAchieveStatusAndAchievedAt; import com.dnd.runus.global.constant.BadgeType; +import com.dnd.runus.presentation.v1.badge.dto.response.AchievedBadge; import com.dnd.runus.presentation.v1.badge.dto.response.AchievedBadgesResponse; -import com.dnd.runus.presentation.v1.badge.dto.response.AllBadgesListResponse; -import com.dnd.runus.presentation.v1.badge.dto.response.AllBadgesListResponse.BadgeWithAchievedStatus; +import com.dnd.runus.presentation.v1.badge.dto.response.AllBadgesListResponse.BadgesWithType; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -16,15 +15,11 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import java.time.LocalDate; -import java.time.LocalDateTime; import java.time.OffsetDateTime; +import java.util.Arrays; import java.util.List; -import static com.dnd.runus.global.constant.TimeConstant.SERVER_TIMEZONE_ID; -import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.BDDMockito.given; @@ -55,9 +50,9 @@ void getAchievedBadges() { AchievedBadgesResponse achievedBadgesResponse = badgeService.getAchievedBadges(1L); // then - List achievedBadges = achievedBadgesResponse.badges(); - AchievedBadgesResponse.AchievedBadge achievedBadge1 = achievedBadges.get(0); - AchievedBadgesResponse.AchievedBadge achievedBadge2 = achievedBadges.get(1); + List achievedBadges = achievedBadgesResponse.badges(); + AchievedBadge achievedBadge1 = achievedBadges.get(0); + AchievedBadge achievedBadge2 = achievedBadges.get(1); assertEquals("badge1", achievedBadge1.name()); assertEquals("imageUrl1", achievedBadge1.imageUrl()); assertEquals("badge2", achievedBadge2.name()); @@ -75,79 +70,40 @@ void getAchievedBadges_Empty() { AchievedBadgesResponse achievedBadgesResponse = badgeService.getAchievedBadges(1L); // then - List achievedBadges = achievedBadgesResponse.badges(); + List achievedBadges = achievedBadgesResponse.badges(); assertEquals(0, achievedBadges.size()); } - @DisplayName("모든 배지 리스트를 조회 한다.") @Test - void findAllBadges() { + @DisplayName("뱃지 타입 별 획득한 뱃지 리스트 응답한다. 타입에 대해 획득한 뱃지가 없는 경우, 빈리스트를 반환한다.") + void getListOfAllBadges() { // given - long memberId = 1L; - LocalDateTime todayMidnight = LocalDate.now(SERVER_TIMEZONE_ID).atTime(0, 0, 0); - given(badgeRepository.findAllBadgesWithAchieveStatusByMemberId(memberId)) + Badge badge1 = new Badge(1L, "badge1", "description", "imageUrl1", BadgeType.STREAK, 100); + Badge badge2 = new Badge(2L, "badge2", "description", "imageUrl2", BadgeType.DISTANCE_METER, 1000); + + given(badgeAchievementRepository.findByMemberIdOrderByBadgeTypeAndAchievedAt(1L)) .willReturn(List.of( - new BadgeWithAchieveStatusAndAchievedAt( - new Badge(1L, "streak_today_achieved", "description", "imageUrl", BadgeType.STREAK, 0), - true, - todayMidnight), - new BadgeWithAchieveStatusAndAchievedAt( - new Badge( - 1L, "streak_6daysAgo_achieved", "description", "imageUrl", BadgeType.STREAK, 0), - true, - todayMidnight.minusDays(6)), - new BadgeWithAchieveStatusAndAchievedAt( - new Badge( - 1L, "streak_8daysAgo_achieved", "description", "imageUrl", BadgeType.STREAK, 0), - true, - todayMidnight.minusDays(8)), - new BadgeWithAchieveStatusAndAchievedAt( - new Badge(1L, "streak_NotAchieved", "description", "imageUrl", BadgeType.STREAK, 0), - false, - null), - new BadgeWithAchieveStatusAndAchievedAt( - new Badge(1L, "distance", "description", "imageUrl", BadgeType.DISTANCE_METER, 0), - false, - null), - new BadgeWithAchieveStatusAndAchievedAt( - new Badge(1L, "personal", "description", "imageUrl", BadgeType.PERSONAL_RECORD, 0), - false, - null), - new BadgeWithAchieveStatusAndAchievedAt( - new Badge(1L, "duration", "description", "imageUrl", BadgeType.DURATION_SECONDS, 0), - false, - null))); + new BadgeAchievement.OnlyBadge(1L, badge1, OffsetDateTime.now(), OffsetDateTime.now()), + new BadgeAchievement.OnlyBadge(2L, badge2, OffsetDateTime.now(), OffsetDateTime.now()))); // when - AllBadgesListResponse result = badgeService.getListOfAllBadges(memberId); + List badgesList = badgeService.getListOfAllBadges(1L).badgesList(); // then - List recencyBadges = result.recencyBadges(); - List streakBadges = result.streakBadges(); - List distanceBadges = result.distanceBadges(); - List personalBadges = result.personalBadges(); - List durationBadges = result.durationBadges(); - - assertFalse(recencyBadges.isEmpty()); - assertFalse(streakBadges.isEmpty()); - assertFalse(distanceBadges.isEmpty()); - assertFalse(personalBadges.isEmpty()); - assertFalse(durationBadges.isEmpty()); - assertTrue(result.levelBadges().isEmpty()); - - assertThat(recencyBadges.size()).isEqualTo(2); - - assertThat(streakBadges.size()).isEqualTo(4); - streakBadges.forEach(v -> { - if (v.name().contains("achieved")) { - assertTrue(v.isAchieved()); + List typeNames = + Arrays.stream(BadgeType.values()).map(BadgeType::getName).toList(); + List categories = + badgesList.stream().map(BadgesWithType::category).toList(); + // BadgeType에 해당되는 category가 모두 존재하는지 확인 + assertTrue(categories.containsAll(typeNames)); + + badgesList.forEach(badgesWithType -> { + if (BadgeType.STREAK.getName().equals(badgesWithType.category()) + || BadgeType.DISTANCE_METER.getName().equals(badgesWithType.category())) { + assertEquals(1, badgesWithType.badges().size()); } else { - assertFalse(v.isAchieved()); + assertEquals(0, badgesWithType.badges().size()); } }); - - assertThat(distanceBadges.size()).isEqualTo(1); - assertThat(personalBadges.size()).isEqualTo(1); - assertThat(durationBadges.size()).isEqualTo(1); } } diff --git a/src/test/java/com/dnd/runus/infrastructure/persistence/domain/badge/BadgeAchievementRepositoryImplTest.java b/src/test/java/com/dnd/runus/infrastructure/persistence/domain/badge/BadgeAchievementRepositoryImplTest.java index d9e84442..806354bb 100644 --- a/src/test/java/com/dnd/runus/infrastructure/persistence/domain/badge/BadgeAchievementRepositoryImplTest.java +++ b/src/test/java/com/dnd/runus/infrastructure/persistence/domain/badge/BadgeAchievementRepositoryImplTest.java @@ -72,10 +72,10 @@ void beforeEach() { BadgeEntity.from(new Badge(0L, "testBadge2", "testBadge2", "tesUrl2", BadgeType.DISTANCE_METER, 2)); BadgeEntity badgeEntity3 = - BadgeEntity.from(new Badge(0L, "testBadge3", "testBadge3", "tesUrl3", BadgeType.DISTANCE_METER, 3)); + BadgeEntity.from(new Badge(0L, "testBadge3", "testBadge3", "tesUrl3", BadgeType.STREAK, 3)); BadgeEntity badgeEntity4 = - BadgeEntity.from(new Badge(0L, "testBadge4", "testBadge4", "tesUrl4", BadgeType.DISTANCE_METER, 4)); + BadgeEntity.from(new Badge(0L, "testBadge4", "testBadge4", "tesUrl4", BadgeType.STREAK, 4)); entityManager.persist(badgeEntity1); entityManager.persist(badgeEntity2); @@ -117,6 +117,30 @@ void findByMemberIdWithBadgeOrderByAchievedAtLimit() { assertTrue(achievedAt1.isAfter(achievedAt2)); assertTrue(achievedAt2.isAfter(achievedAt3)); } + + @Test + @DisplayName("나의 뱃지 목록을 조회한다: 뱃지 타입순, 최신 획득한 순으로 데이터를 리턴한다.") + void findBadgesList() { + // given + badgeAchievementRepository.save(new BadgeAchievement(badge1, savedMember)); + badgeAchievementRepository.save(new BadgeAchievement(badge2, savedMember)); + badgeAchievementRepository.save(new BadgeAchievement(badge3, savedMember)); + badgeAchievementRepository.save(new BadgeAchievement(badge4, savedMember)); + + // when + List achievedBadges = + badgeAchievementRepository.findByMemberIdOrderByBadgeTypeAndAchievedAt(savedMember.memberId()); + + // then + OnlyBadge achieved1 = achievedBadges.get(0); + OnlyBadge achieved2 = achievedBadges.get(1); + OnlyBadge achieved3 = achievedBadges.get(2); + OnlyBadge achieved4 = achievedBadges.get(3); + + assertTrue(achieved1.badge().type().compareTo(achieved3.badge().type()) < 0); + assertTrue(achieved1.createdAt().isAfter(achieved2.createdAt())); + assertTrue(achieved3.createdAt().isAfter(achieved4.createdAt())); + } } @Nested diff --git a/src/test/java/com/dnd/runus/infrastructure/persistence/domain/badge/BadgeRepositoryTest.java b/src/test/java/com/dnd/runus/infrastructure/persistence/domain/badge/BadgeRepositoryTest.java deleted file mode 100644 index 696a1820..00000000 --- a/src/test/java/com/dnd/runus/infrastructure/persistence/domain/badge/BadgeRepositoryTest.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.dnd.runus.infrastructure.persistence.domain.badge; - -import com.dnd.runus.domain.badge.Badge; -import com.dnd.runus.domain.badge.BadgeAchievement; -import com.dnd.runus.domain.badge.BadgeAchievementRepository; -import com.dnd.runus.domain.badge.BadgeRepository; -import com.dnd.runus.domain.badge.BadgeWithAchieveStatusAndAchievedAt; -import com.dnd.runus.domain.member.Member; -import com.dnd.runus.domain.member.MemberRepository; -import com.dnd.runus.global.constant.BadgeType; -import com.dnd.runus.global.constant.MemberRole; -import com.dnd.runus.infrastructure.persistence.annotation.RepositoryTest; -import com.dnd.runus.infrastructure.persistence.jpa.badge.entity.BadgeEntity; -import jakarta.persistence.EntityManager; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.CriteriaQuery; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; - -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.AssertionsForClassTypes.tuple; - -@RepositoryTest -class BadgeRepositoryTest { - @Autowired - private BadgeRepository badgeRepository; - - @Autowired - private BadgeAchievementRepository badgeAchievementRepository; - - @Autowired - private MemberRepository memberRepository; - - @Autowired - private EntityManager em; - - private Badge badge1; - private Badge badge2; - - @BeforeEach - void setUp() { - - // insert Badge test data - em.persist(BadgeEntity.from(new Badge(0, "badge1", "description1", "imageUrl", BadgeType.STREAK, 0))); - em.persist(BadgeEntity.from(new Badge(0, "badge2", "description2", "imageUrl", BadgeType.STREAK, 0))); - em.flush(); - - CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder(); - - CriteriaQuery query = criteriaBuilder.createQuery(BadgeEntity.class); - List selectAll = - em.createQuery(query.select(query.from(BadgeEntity.class))).getResultList(); - - for (BadgeEntity badgeEntity : selectAll) { - if (badgeEntity.getName().equals("badge1")) { - badge1 = badgeEntity.toDomain(); - } else { - badge2 = badgeEntity.toDomain(); - } - } - } - - @DisplayName("모든 배지 리스트를 조회 한다. 사용자가 해당 배지를 성취했을 경우 isAchieved는 true를 반환한다.") - @Test - void findAllBadges() { - // given - Member member = memberRepository.save(new Member(MemberRole.USER, "member1")); - badgeAchievementRepository.save(new BadgeAchievement(badge1, member)); - - // when - List allBadges = - badgeRepository.findAllBadgesWithAchieveStatusByMemberId(member.memberId()); - - // then - assertThat(allBadges.size()).isEqualTo(2); - assertThat(allBadges) - .extracting(o -> o.badge().badgeId(), BadgeWithAchieveStatusAndAchievedAt::isAchieved) - .containsExactlyInAnyOrder(tuple(badge1.badgeId(), true), tuple(badge2.badgeId(), false)); - } -}