diff --git a/src/main/java/com/dnd/runus/application/running/RunningRecordService.java b/src/main/java/com/dnd/runus/application/running/RunningRecordService.java
index d43b9537..d72a3094 100644
--- a/src/main/java/com/dnd/runus/application/running/RunningRecordService.java
+++ b/src/main/java/com/dnd/runus/application/running/RunningRecordService.java
@@ -3,7 +3,9 @@
import com.dnd.runus.application.running.dto.RunningResultDto;
import com.dnd.runus.application.running.event.RunningRecordAddedEvent;
import com.dnd.runus.domain.challenge.Challenge;
+import com.dnd.runus.domain.challenge.ChallengeCondition;
import com.dnd.runus.domain.challenge.ChallengeRepository;
+import com.dnd.runus.domain.challenge.ChallengeType;
import com.dnd.runus.domain.challenge.ChallengeWithCondition;
import com.dnd.runus.domain.challenge.GoalMetricType;
import com.dnd.runus.domain.challenge.achievement.ChallengeAchievement;
@@ -19,13 +21,15 @@
import com.dnd.runus.domain.running.DailyRunningRecordSummary;
import com.dnd.runus.domain.running.RunningRecord;
import com.dnd.runus.domain.running.RunningRecordRepository;
+import com.dnd.runus.global.exception.BusinessException;
import com.dnd.runus.global.exception.NotFoundException;
+import com.dnd.runus.global.exception.type.ErrorType;
import com.dnd.runus.presentation.v1.running.dto.WeeklyRunningRatingDto;
+import com.dnd.runus.presentation.v1.running.dto.request.RunningAchievementMode;
import com.dnd.runus.presentation.v1.running.dto.request.RunningRecordRequestV1;
import com.dnd.runus.presentation.v1.running.dto.request.RunningRecordWeeklySummaryType;
import com.dnd.runus.presentation.v1.running.dto.response.*;
import com.dnd.runus.presentation.v2.running.dto.request.RunningRecordRequestV2;
-import com.dnd.runus.presentation.v2.running.dto.request.RunningRecordRequestV2.ChallengeAchievedDto;
import com.dnd.runus.presentation.v2.running.dto.request.RunningRecordRequestV2.GoalAchievedDto;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
@@ -79,7 +83,7 @@ public RunningRecordService(
}
@Transactional(readOnly = true)
- public RunningRecordQueryResponse getRunningRecord(long memberId, long runningRecordId) {
+ public RunningResultDto getRunningRecord(long memberId, long runningRecordId) {
RunningRecord runningRecord = runningRecordRepository
.findById(runningRecordId)
.filter(r -> r.member().memberId() == memberId)
@@ -97,7 +101,27 @@ public RunningRecordQueryResponse getRunningRecord(long memberId, long runningRe
.orElse(null)
: null;
- return RunningRecordQueryResponse.of(runningRecord, challengeAchievement, goalAchievement);
+ RunningAchievementMode runningAchievementMode = (challengeAchievement != null)
+ ? RunningAchievementMode.CHALLENGE
+ : (goalAchievement != null) ? RunningAchievementMode.GOAL : RunningAchievementMode.NORMAL;
+
+ switch (runningAchievementMode) {
+ case CHALLENGE -> {
+ return RunningResultDto.of(
+ runningRecord,
+ challengeAchievement,
+ calChallengeAchievementPercentage(memberId, challengeAchievement));
+ }
+ case GOAL -> {
+ int achievedGoalValue = goalAchievement.goalMetricType().getActualValue(runningRecord);
+ return RunningResultDto.of(
+ runningRecord,
+ goalAchievement,
+ calPercentage(achievedGoalValue, goalAchievement.achievementValue()));
+ }
+ }
+
+ return RunningResultDto.from(runningRecord);
}
@Transactional(readOnly = true)
@@ -223,16 +247,22 @@ public RunningResultDto addRunningRecordV2(long memberId, RunningRecordRequestV2
switch (request.achievementMode()) {
case CHALLENGE -> {
- ChallengeAchievedDto challengeAchievedForAdd = request.challengeValues();
Challenge challenge = challengeRepository
- .findById(challengeAchievedForAdd.challengeId())
- .orElseThrow(
- () -> new NotFoundException(Challenge.class, challengeAchievedForAdd.challengeId()));
+ .findById(request.challengeValues().challengeId())
+ .orElseThrow(() -> new NotFoundException(
+ Challenge.class, request.challengeValues().challengeId()));
+ if (!challenge.isActive()) {
+ throw new BusinessException(ErrorType.CHALLENGE_NOT_ACTIVE);
+ }
- ChallengeAchievement challengeAchievement = challengeAchievementRepository.save(
- new ChallengeAchievement(challenge, record, challengeAchievedForAdd.isSuccess()));
+ ChallengeAchievement challengeAchievement =
+ challengeAchievementRepository.save(new ChallengeAchievement(
+ challenge, record, request.challengeValues().isSuccess()));
- return RunningResultDto.of(record, challengeAchievement);
+ return RunningResultDto.of(
+ record,
+ challengeAchievement,
+ calChallengeAchievementPercentage(memberId, challengeAchievement));
}
case GOAL -> {
GoalAchievedDto goalAchievedForAdd = request.goalValues();
@@ -244,7 +274,10 @@ public RunningResultDto addRunningRecordV2(long memberId, RunningRecordRequestV2
: goalAchievedForAdd.goalTime(),
goalAchievedForAdd.isSuccess()));
- return RunningResultDto.of(record, goalAchievement);
+ int achievedGoalValue = goalAchievement.goalMetricType().getActualValue(record);
+
+ return RunningResultDto.of(
+ record, goalAchievement, calPercentage(achievedGoalValue, goalAchievement.achievementValue()));
}
}
return RunningResultDto.from(record);
@@ -337,4 +370,54 @@ private GoalAchievement handleGoalMode(RunningRecord runningRecord, Integer goal
GoalAchievement goalAchievement = new GoalAchievement(runningRecord, goalMetricType, goalValue, isAchieved);
return goalAchievementRepository.save(goalAchievement);
}
+
+ /**
+ * 챌린지 퍼센테이지 계산(V2 이후에서 사용)
+ * 어제 러닝 기록 이기기 관련 챌린지면 챌린지 목표값 재등록(기본 목표 값 + 어제 러닝 기록)한 후 계산
+ * @return Double(퍼센테이지 계산하기 힘든, 2 이전에 저장된 챌린지같은 경우 null로 리턴)
+ */
+ private Double calChallengeAchievementPercentage(long memberId, ChallengeAchievement challengeAchievement) {
+
+ Challenge challenge = challengeAchievement.challenge();
+ ChallengeWithCondition challengeWithCondition = challengeRepository
+ .findChallengeWithConditionsByChallengeId(challenge.challengeId())
+ .orElseThrow(() -> new NotFoundException(Challenge.class, challenge.challengeId()));
+
+ // DISTANCE_IN_TIME, PACE 챌린지인지 확인
+ // (v2이전에 퍼센테이지를 표현할 수 없는 애들일 경우, 퍼센테이지 null로 리턴함)
+ if (challenge.challengeType() == ChallengeType.DISTANCE_IN_TIME
+ || challengeWithCondition.conditions().stream()
+ .anyMatch(condition -> !condition.goalMetricType().hasPercentage())) {
+ return null;
+ }
+
+ ChallengeCondition condition = challengeWithCondition.conditions().getFirst();
+ RunningRecord runningRecord = challengeAchievement.runningRecord();
+
+ if (challenge.isDefeatYesterdayChallenge()) {
+ // 어제 러닝 기록 이기기 관련 챌린지면, 챌린지 목표값 재등록(어제 기록 + 목표값)
+ OffsetDateTime runningDate = runningRecord
+ .startAt()
+ .toLocalDate()
+ .atStartOfDay(runningRecord.startAt().getZone())
+ .toOffsetDateTime();
+
+ RunningRecord preRunningRecord =
+ runningRecordRepository
+ .findByMemberIdAndStartAtBetween(memberId, runningDate.minusDays(1), runningDate)
+ .stream()
+ .findFirst()
+ .orElseThrow(() -> new NotFoundException("이전 러닝 기록을 가져올 수 없습니다."));
+ condition.registerComparisonValue(condition.goalMetricType().getActualValue(preRunningRecord));
+ }
+
+ int achievedValue = condition.goalMetricType().getActualValue(runningRecord);
+ return calPercentage(achievedValue, condition.comparisonValue());
+ }
+
+ private double calPercentage(double part, double total) {
+ if (total <= 0) return 0;
+ double percentage = part / total;
+ return percentage > 1 ? 1 : percentage;
+ }
}
diff --git a/src/main/java/com/dnd/runus/application/running/dto/RunningResultDto.java b/src/main/java/com/dnd/runus/application/running/dto/RunningResultDto.java
index d410507b..c66c66b4 100644
--- a/src/main/java/com/dnd/runus/application/running/dto/RunningResultDto.java
+++ b/src/main/java/com/dnd/runus/application/running/dto/RunningResultDto.java
@@ -9,33 +9,38 @@ public record RunningResultDto(
RunningRecord runningRecord,
com.dnd.runus.presentation.v1.running.dto.request.RunningAchievementMode runningAchievementMode,
ChallengeAchievement challengeAchievement,
- GoalAchievement goalAchievement
+ GoalAchievement goalAchievement,
+ Double percentage
) {
public static RunningResultDto from(RunningRecord runningRecord) {
return new RunningResultDto(
runningRecord,
com.dnd.runus.presentation.v1.running.dto.request.RunningAchievementMode.NORMAL,
null,
+ null,
null
);
}
public static RunningResultDto of(RunningRecord runningRecord,
- ChallengeAchievement challengeAchievement) {
+ ChallengeAchievement challengeAchievement, Double percentage) {
return new RunningResultDto(
runningRecord,
com.dnd.runus.presentation.v1.running.dto.request.RunningAchievementMode.CHALLENGE,
challengeAchievement,
- null
+ null,
+ percentage
);
}
- public static RunningResultDto of(RunningRecord runningRecord, GoalAchievement goalAchievement) {
+ public static RunningResultDto of(RunningRecord runningRecord,
+ GoalAchievement goalAchievement, Double percentage) {
return new RunningResultDto(
runningRecord,
com.dnd.runus.presentation.v1.running.dto.request.RunningAchievementMode.GOAL,
null,
- goalAchievement
+ goalAchievement,
+ percentage
);
}
}
diff --git a/src/main/java/com/dnd/runus/global/exception/type/ErrorType.java b/src/main/java/com/dnd/runus/global/exception/type/ErrorType.java
index 310ebf4b..d896949a 100644
--- a/src/main/java/com/dnd/runus/global/exception/type/ErrorType.java
+++ b/src/main/java/com/dnd/runus/global/exception/type/ErrorType.java
@@ -50,6 +50,9 @@ public enum ErrorType {
GOAL_VALUES_REQUIRED_IN_GOAL_MODE(BAD_REQUEST, DEBUG, "RUNNING_005", "개인 목표 모드에서, 개인 목표 달성값은 필수 잆니다."),
CHALLENGE_VALUES_REQUIRED_IN_CHALLENGE_MODE(BAD_REQUEST, DEBUG, "RUNNING_006", "챌린지 모드에서, 챌린지 달성값은 필수 입니다."),
+ // ChallengeErrorType
+ CHALLENGE_NOT_ACTIVE(CONFLICT, DEBUG, "CHALLENGE_001", "해당 챌린지는 현재 활성화된 챌린지가 아닙니다."),
+
// WeatherErrorType
WEATHER_API_ERROR(SERVICE_UNAVAILABLE, WARN, "WEATHER_001", "날씨 API 호출 중 오류가 발생했습니다"),
;
diff --git a/src/main/java/com/dnd/runus/presentation/v1/running/RunningRecordController.java b/src/main/java/com/dnd/runus/presentation/v1/running/RunningRecordController.java
index a323050f..87a2cc87 100644
--- a/src/main/java/com/dnd/runus/presentation/v1/running/RunningRecordController.java
+++ b/src/main/java/com/dnd/runus/presentation/v1/running/RunningRecordController.java
@@ -31,7 +31,7 @@ public class RunningRecordController {
@GetMapping("/{runningRecordId}")
@Operation(summary = "러닝 기록 상세 조회", description = "RunngingRecord id로 러닝 상세 기록을 조회합니다.")
public RunningRecordQueryResponse getRunningRecord(@MemberId long memberId, @PathVariable long runningRecordId) {
- return runningRecordService.getRunningRecord(memberId, runningRecordId);
+ return RunningRecordQueryResponse.from(runningRecordService.getRunningRecord(memberId, runningRecordId));
}
@GetMapping("monthly-dates")
diff --git a/src/main/java/com/dnd/runus/presentation/v1/running/dto/ChallengeDto.java b/src/main/java/com/dnd/runus/presentation/v1/running/dto/ChallengeDto.java
index 055bd9a7..ccea6cd8 100644
--- a/src/main/java/com/dnd/runus/presentation/v1/running/dto/ChallengeDto.java
+++ b/src/main/java/com/dnd/runus/presentation/v1/running/dto/ChallengeDto.java
@@ -1,21 +1,31 @@
package com.dnd.runus.presentation.v1.running.dto;
+import com.dnd.runus.domain.challenge.achievement.ChallengeAchievement;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
public record ChallengeDto(
- @Schema(description = "챌린지 ID")
- long challengeId,
- @NotBlank
- @Schema(description = "챌린지 이름", example = "오늘 30분 동안 뛰기")
- String title,
- @NotBlank
- @Schema(description = "챌린지 결과 문구", example = "성공했어요!")
- String subTitle,
- @NotBlank
- @Schema(description = "챌린지 아이콘 이미지 URL")
- String iconUrl,
- @Schema(description = "챌린지 성공 여부")
- boolean isSuccess
+ @Schema(description = "챌린지 ID")
+ long challengeId,
+ @NotBlank
+ @Schema(description = "챌린지 이름", example = "오늘 30분 동안 뛰기")
+ String title,
+ @NotBlank
+ @Schema(description = "챌린지 결과 문구", example = "성공했어요!")
+ String subTitle,
+ @NotBlank
+ @Schema(description = "챌린지 아이콘 이미지 URL")
+ String iconUrl,
+ @Schema(description = "챌린지 성공 여부")
+ boolean isSuccess
) {
+ public static ChallengeDto from(ChallengeAchievement achievement) {
+ return new ChallengeDto(
+ achievement.challenge().challengeId(),
+ achievement.challenge().name(),
+ achievement.description(),
+ achievement.challenge().imageUrl(),
+ achievement.isSuccess()
+ );
+ }
}
diff --git a/src/main/java/com/dnd/runus/presentation/v1/running/dto/GoalResultDto.java b/src/main/java/com/dnd/runus/presentation/v1/running/dto/GoalResultDto.java
index a72a052a..6d42a5da 100644
--- a/src/main/java/com/dnd/runus/presentation/v1/running/dto/GoalResultDto.java
+++ b/src/main/java/com/dnd/runus/presentation/v1/running/dto/GoalResultDto.java
@@ -1,17 +1,26 @@
package com.dnd.runus.presentation.v1.running.dto;
+import com.dnd.runus.domain.goalAchievement.GoalAchievement;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
public record GoalResultDto(
- @Schema(description = "설정된 목표 제목", example = "2.5km 달성")
- String title,
- @Schema(description = "설정된 목표 결과 문구", example = "성공했어요!")
- String subTitle,
- @NotBlank
- @Schema(description = "챌린지 아이콘 이미지 URL")
- String iconUrl,
- @Schema(description = "설정된 목표 성공 여부")
- boolean isSuccess
+ @Schema(description = "설정된 목표 제목", example = "2.5km 달성")
+ String title,
+ @Schema(description = "설정된 목표 결과 문구", example = "성공했어요!")
+ String subTitle,
+ @NotBlank
+ @Schema(description = "챌린지 아이콘 이미지 URL")
+ String iconUrl,
+ @Schema(description = "설정된 목표 성공 여부")
+ boolean isSuccess
) {
+ public static GoalResultDto from(GoalAchievement achievement) {
+ return new GoalResultDto(
+ achievement.getTitle(),
+ achievement.getDescription(),
+ achievement.getIconUrl(),
+ achievement.isAchieved()
+ );
+ }
}
diff --git a/src/main/java/com/dnd/runus/presentation/v1/running/dto/response/RunningRecordQueryResponse.java b/src/main/java/com/dnd/runus/presentation/v1/running/dto/response/RunningRecordQueryResponse.java
index dbef30d5..091a28fd 100644
--- a/src/main/java/com/dnd/runus/presentation/v1/running/dto/response/RunningRecordQueryResponse.java
+++ b/src/main/java/com/dnd/runus/presentation/v1/running/dto/response/RunningRecordQueryResponse.java
@@ -1,7 +1,6 @@
package com.dnd.runus.presentation.v1.running.dto.response;
-import com.dnd.runus.domain.challenge.achievement.ChallengeAchievement;
-import com.dnd.runus.domain.goalAchievement.GoalAchievement;
+import com.dnd.runus.application.running.dto.RunningResultDto;
import com.dnd.runus.domain.running.RunningRecord;
import com.dnd.runus.global.constant.RunningEmoji;
import com.dnd.runus.presentation.v1.running.dto.ChallengeDto;
@@ -32,45 +31,18 @@ public record RunningRecordQueryResponse(
@NotNull
RunningRecordMetricsDto runningData
) {
- public static RunningRecordQueryResponse from(RunningRecord runningRecord) {
- return buildResponse(runningRecord, null, null, RunningAchievementMode.NORMAL);
- }
-
- public static RunningRecordQueryResponse of(RunningRecord runningRecord, ChallengeAchievement achievement) {
- return buildResponse(runningRecord,
- new ChallengeDto(
- achievement.challenge().challengeId(),
- achievement.challenge().name(),
- achievement.description(),
- achievement.challenge().imageUrl(),
- achievement.isSuccess()
- ),
- null,
- RunningAchievementMode.CHALLENGE
- );
- }
- public static RunningRecordQueryResponse of(RunningRecord runningRecord, GoalAchievement achievement) {
- return buildResponse(runningRecord,
- null,
- new GoalResultDto(
- achievement.getTitle(),
- achievement.getDescription(),
- achievement.getIconUrl(),
- achievement.isAchieved()
- ),
- RunningAchievementMode.GOAL
+ public static RunningRecordQueryResponse from(RunningResultDto runningResult) {
+ return buildResponse(
+ runningResult.runningRecord(),
+ runningResult.challengeAchievement() == null ? null
+ : ChallengeDto.from(runningResult.challengeAchievement()),
+ runningResult.goalAchievement() == null ? null
+ : GoalResultDto.from(runningResult.goalAchievement()),
+ runningResult.runningAchievementMode()
);
}
- public static RunningRecordQueryResponse of(RunningRecord runningRecord, ChallengeAchievement challengeAchievement, GoalAchievement goalAchievement) {
- if (challengeAchievement != null) {
- return of(runningRecord, challengeAchievement);
- } else if (goalAchievement != null) {
- return of(runningRecord, goalAchievement);
- }
- return from(runningRecord);
- }
private static RunningRecordQueryResponse buildResponse(RunningRecord runningRecord, ChallengeDto challenge, GoalResultDto goal, RunningAchievementMode achievementMode) {
return new RunningRecordQueryResponse(
@@ -89,4 +61,5 @@ private static RunningRecordQueryResponse buildResponse(RunningRecord runningRec
)
);
}
+
}
diff --git a/src/main/java/com/dnd/runus/presentation/v2/running/RunningRecordControllerV2.java b/src/main/java/com/dnd/runus/presentation/v2/running/RunningRecordControllerV2.java
index 5caf2f7f..db47200e 100644
--- a/src/main/java/com/dnd/runus/presentation/v2/running/RunningRecordControllerV2.java
+++ b/src/main/java/com/dnd/runus/presentation/v2/running/RunningRecordControllerV2.java
@@ -14,6 +14,7 @@
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -27,6 +28,12 @@ public class RunningRecordControllerV2 {
private final RunningRecordServiceV2 runningRecordService2;
private final RunningRecordService runningRecordService;
+ @GetMapping("/{runningRecordId}")
+ @Operation(summary = "러닝 기록 상세 조회", description = "RunngingRecord id로 러닝 상세 기록을 조회합니다.")
+ public RunningRecordResultResponseV2 getRunningRecord(@MemberId long memberId, @PathVariable long runningRecordId) {
+ return RunningRecordResultResponseV2.from(runningRecordService.getRunningRecord(memberId, runningRecordId));
+ }
+
@Operation(summary = "이번 달 러닝 기록 조회(홈화면) V2", description = """
홈화면의 이번 달 러닝 기록을 조회 합니다.
""")
@@ -47,9 +54,9 @@ public RunningRecordMonthlySummaryResponseV2 getMonthlyRunningSummary(@MemberId
"""
러닝 기록을 추가합니다.
러닝 기록은 시작 시간, 종료 시간, 러닝 평가(emotion), 러닝 데이터 등으로 구성됩니다.
- 챌린지 모드가 normal : challengeValues, goalValues 둘다 null
- 챌린지 모드가 challenge : challengeValues 필수 값
- 챌린지 모드가 goal : goalValues 필수 값
+ normal : challengeValues, goalValues 둘다 null
+ challenge : challengeValues 필수 값
+ goal : goalValues 필수 값
러닝 데이터는 위치, 거리, 시간, 칼로리, 평균 페이스, 러닝 경로로 구성됩니다.
러닝 기록 추가에 성공하면 러닝 기록 ID, 기록 정보를 반환합니다.
""")
@@ -58,7 +65,8 @@ public RunningRecordMonthlySummaryResponseV2 getMonthlyRunningSummary(@MemberId
ErrorType.CHALLENGE_VALUES_REQUIRED_IN_CHALLENGE_MODE,
ErrorType.GOAL_VALUES_REQUIRED_IN_GOAL_MODE,
ErrorType.GOAL_TIME_AND_DISTANCE_BOTH_EXIST,
- ErrorType.ROUTE_MUST_HAVE_AT_LEAST_TWO_COORDINATES
+ ErrorType.ROUTE_MUST_HAVE_AT_LEAST_TWO_COORDINATES,
+ ErrorType.CHALLENGE_NOT_ACTIVE
})
@PostMapping
@ResponseStatus(HttpStatus.OK)
diff --git a/src/main/java/com/dnd/runus/presentation/v2/running/dto/AchievementResultDto.java b/src/main/java/com/dnd/runus/presentation/v2/running/dto/AchievementResultDto.java
new file mode 100644
index 00000000..a2fb9c27
--- /dev/null
+++ b/src/main/java/com/dnd/runus/presentation/v2/running/dto/AchievementResultDto.java
@@ -0,0 +1,23 @@
+package com.dnd.runus.presentation.v2.running.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import org.springframework.lang.Nullable;
+
+/**
+ * 챌린지, 목표 달성 결과 통합 DTO
+ */
+public record AchievementResultDto(
+ @Schema(description = "챌린지 이름 또는 설정된 목표 제목", examples = {"오늘 30분 동안 뛰기", "2.5km 달성"})
+ String title,
+ @Schema(description = "결과 문구", examples = {"정말 대단해요! 잘하셨어요", "아쉬워요. 내일 다시 도전해보세요!"})
+ String subTitle,
+ @Schema(description = "아이콘 이미지 URL")
+ String iconUrl,
+ @Schema(description = "성공 여부")
+ boolean isSuccess,
+ @Nullable
+ @Schema(description = "퍼센테이지 값, V2 이전에 퍼센테이지를 나타낼 수 없는 챌린지일 경우 null값을 리턴합니다.")
+ Double percentage
+) {
+
+}
diff --git a/src/main/java/com/dnd/runus/presentation/v2/running/dto/RouteDtoV2.java b/src/main/java/com/dnd/runus/presentation/v2/running/dto/RouteDtoV2.java
index c4ce3256..1ac8bf8f 100644
--- a/src/main/java/com/dnd/runus/presentation/v2/running/dto/RouteDtoV2.java
+++ b/src/main/java/com/dnd/runus/presentation/v2/running/dto/RouteDtoV2.java
@@ -14,7 +14,7 @@ public record RouteDtoV2(
) {
public record Point(double longitude, double latitude) {
public static Point from(CoordinatePoint point) {
- return new Point(point.longitude(), point.longitude());
+ return new Point(point.longitude(), point.latitude());
}
}
}
diff --git a/src/main/java/com/dnd/runus/presentation/v2/running/dto/request/RunningRecordRequestV2.java b/src/main/java/com/dnd/runus/presentation/v2/running/dto/request/RunningRecordRequestV2.java
index 7265748a..c6100820 100644
--- a/src/main/java/com/dnd/runus/presentation/v2/running/dto/request/RunningRecordRequestV2.java
+++ b/src/main/java/com/dnd/runus/presentation/v2/running/dto/request/RunningRecordRequestV2.java
@@ -31,7 +31,8 @@ public record RunningRecordRequestV2(
com.dnd.runus.presentation.v1.running.dto.request.RunningAchievementMode achievementMode,
@Schema(description = "챌린지 데이터, 챌린지를 하지 않은 경우 null이나 필드 없이 보내주세요")
ChallengeAchievedDto challengeValues,
- @Schema(description = "목표 데이터, 목표를 설정하지 않은 경우 null이나 필드 없이 보내주세요")
+ @Schema(description = "목표 데이터, 목표를 설정하지 않은 경우 null이나 필드 없이 보내주세요. "
+ + "goalDistance(거리) 또는 goalTime(시간)값 둘 중 하나는 null이어야 합니다.")
GoalAchievedDto goalValues,
@NotNull
RunningRecordMetrics runningData
@@ -52,7 +53,8 @@ public record RunningRecordRequestV2(
if(goalValues == null) {
throw new BusinessException(ErrorType.GOAL_VALUES_REQUIRED_IN_GOAL_MODE);
}
- if (goalValues.goalDistance() == null && goalValues.goalTime() == null) {
+ if ((goalValues.goalDistance() == null && goalValues.goalTime() == null)
+ || (goalValues.goalDistance() != null && goalValues.goalTime() != null)) {
throw new BusinessException(ErrorType.GOAL_TIME_AND_DISTANCE_BOTH_EXIST);
}
}
diff --git a/src/main/java/com/dnd/runus/presentation/v2/running/dto/response/RunningRecordResultResponseV2.java b/src/main/java/com/dnd/runus/presentation/v2/running/dto/response/RunningRecordResultResponseV2.java
index 57fd0841..6390b7ff 100644
--- a/src/main/java/com/dnd/runus/presentation/v2/running/dto/response/RunningRecordResultResponseV2.java
+++ b/src/main/java/com/dnd/runus/presentation/v2/running/dto/response/RunningRecordResultResponseV2.java
@@ -7,8 +7,8 @@
import com.dnd.runus.domain.goalAchievement.GoalAchievement;
import com.dnd.runus.domain.running.RunningRecord;
import com.dnd.runus.global.constant.RunningEmoji;
-import com.dnd.runus.presentation.v1.running.dto.ChallengeDto;
-import com.dnd.runus.presentation.v1.running.dto.GoalResultDto;
+import com.dnd.runus.presentation.v1.running.dto.request.RunningAchievementMode;
+import com.dnd.runus.presentation.v2.running.dto.AchievementResultDto;
import com.dnd.runus.presentation.v2.running.dto.RouteDtoV2;
import com.dnd.runus.presentation.v2.running.dto.RouteDtoV2.Point;
import io.swagger.v3.oas.annotations.media.Schema;
@@ -29,12 +29,9 @@ public record RunningRecordResultResponseV2(
RunningEmoji emotion,
@NotNull
@Schema(description = "달성 모드, normal: 일반(목표 설정 X), challenge: 챌린지, goal: 목표")
- com.dnd.runus.presentation.v1.running.dto.request.RunningAchievementMode achievementMode,
- //todo 챌린지, 및 goal 관련해서 다시 구성
- @Schema(description = "챌린지 정보, achievementMode가 challenge인 경우에만 값이 존재합니다.")
- ChallengeDto challenge,
- @Schema(description = "목표 결과 정보, achievementMode가 goal인 경우에만 값이 존재합니다.")
- GoalResultDto goal,
+ RunningAchievementMode achievementMode,
+ @Schema(description = "달성 값(챌린지 또는 목표), achievementMode가 challenge 또는 goal인 경우에만 값이 존재합니다.")
+ AchievementResultDto achievementResult,
@NotNull
RunningRecordMetrics runningData
) {
@@ -61,10 +58,7 @@ public static RunningRecordResultResponseV2 from(RunningResultDto runningRecordR
runningRecord.endAt().toLocalDateTime(),
runningRecord.emoji(),
runningRecordResult.runningAchievementMode(),
- runningRecordResult.runningAchievementMode() != com.dnd.runus.presentation.v1.running.dto.request.RunningAchievementMode.CHALLENGE ? null
- : buildChallengeDto(runningRecordResult.challengeAchievement()),
- runningRecordResult.runningAchievementMode() != com.dnd.runus.presentation.v1.running.dto.request.RunningAchievementMode.GOAL ? null
- : buildGoalResultDto(runningRecordResult.goalAchievement()),
+ buildAchievementResultOf(runningRecordResult),
new RunningRecordMetrics(
runningRecord.averagePace(),
runningRecord.duration(),
@@ -75,32 +69,38 @@ public static RunningRecordResultResponseV2 from(RunningResultDto runningRecordR
);
}
- private static ChallengeDto buildChallengeDto(ChallengeAchievement achievement) {
- if (achievement == null) {
- return null;
- }
- return new ChallengeDto(
- achievement.challenge().challengeId(),
- achievement.challenge().name(),
- achievement.description(),
- achievement.challenge().imageUrl(),
- achievement.isSuccess()
- );
- }
+ private static AchievementResultDto buildAchievementResultOf(RunningResultDto runningRecordResult) {
+ if (runningRecordResult.runningAchievementMode() == RunningAchievementMode.NORMAL) return null;
+ switch (runningRecordResult.runningAchievementMode()) {
+ case GOAL -> {
+ GoalAchievement goalAchievement = runningRecordResult.goalAchievement();
+ if (goalAchievement == null) return null;
+ return new AchievementResultDto(
+ goalAchievement.getTitle(),
+ goalAchievement.getDescription(),
+ goalAchievement.getIconUrl(),
+ goalAchievement.isAchieved(),
+ runningRecordResult.percentage()
+ );
+ }
+ case CHALLENGE -> {
+ ChallengeAchievement challengeAchievement = runningRecordResult.challengeAchievement();
+ if (challengeAchievement == null) return null;
+ return new AchievementResultDto(
+ challengeAchievement.challenge().name(),
+ challengeAchievement.description(),
+ challengeAchievement.challenge().imageUrl(),
+ challengeAchievement.isSuccess(),
+ runningRecordResult.percentage()
+ );
- private static GoalResultDto buildGoalResultDto(GoalAchievement achievement) {
- if (achievement == null) {
- return null;
+ }
+ default -> {
+ return null;
+ }
}
- return new GoalResultDto(
- achievement.getTitle(),
- achievement.getDescription(),
- achievement.getIconUrl(),
- achievement.isAchieved()
- );
}
-
private static List convertRouteDtoListFrom(
List runningRecordRoute) {
// route가 null, empty, 또는 경로데이터를 사용하지 않았을 버전의 데이터 값 인경우 null를 리턴
diff --git a/src/test/java/com/dnd/runus/application/running/RunningRecordServiceTest.java b/src/test/java/com/dnd/runus/application/running/RunningRecordServiceTest.java
index 1e11e21b..a57ed6a7 100644
--- a/src/test/java/com/dnd/runus/application/running/RunningRecordServiceTest.java
+++ b/src/test/java/com/dnd/runus/application/running/RunningRecordServiceTest.java
@@ -24,7 +24,6 @@
import com.dnd.runus.presentation.v1.running.dto.request.RunningRecordWeeklySummaryType;
import com.dnd.runus.presentation.v1.running.dto.response.RunningRecordAddResultResponseV1;
import com.dnd.runus.presentation.v1.running.dto.response.RunningRecordMonthlySummaryResponse;
-import com.dnd.runus.presentation.v1.running.dto.response.RunningRecordQueryResponse;
import com.dnd.runus.presentation.v1.running.dto.response.RunningRecordWeeklySummaryResponse;
import com.dnd.runus.presentation.v2.running.dto.RouteDtoV2;
import com.dnd.runus.presentation.v2.running.dto.request.RunningRecordRequestV2;
@@ -47,6 +46,7 @@
import static com.dnd.runus.global.constant.TimeConstant.SERVER_TIMEZONE;
import static java.time.temporal.ChronoField.DAY_OF_WEEK;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
@@ -117,11 +117,11 @@ void getRunningRecord() {
given(runningRecordRepository.findById(runningRecordId)).willReturn(Optional.of(runningRecord));
// when
- RunningRecordQueryResponse result = runningRecordService.getRunningRecord(memberId, runningRecordId);
+ RunningResultDto result = runningRecordService.getRunningRecord(memberId, runningRecordId);
// then
- assertEquals(runningRecordId, result.runningRecordId());
- assertEquals(runningRecord.emoji(), result.emotion());
+ assertEquals(runningRecordId, result.runningRecord().runningId());
+ assertEquals(runningRecord.emoji(), result.runningRecord().emoji());
}
@Test
@@ -156,6 +156,7 @@ void getRunningRecord_challenge() {
// given
long memberId = 1;
long runningRecordId = 1;
+ Challenge challenge = new Challenge(1L, "challenge", "image", true, ChallengeType.TODAY);
Member member = new Member(memberId, MemberRole.USER, "nickname1", OffsetDateTime.now(), OffsetDateTime.now());
RunningRecord runningRecord = new RunningRecord(
1L,
@@ -171,23 +172,126 @@ void getRunningRecord_challenge() {
"end location",
RunningEmoji.VERY_GOOD);
- ChallengeAchievement.Status challengeAchievementStatus = new ChallengeAchievement.Status(
- 1L, new Challenge(1L, "challenge", "image", true, ChallengeType.TODAY), true);
+ ChallengeAchievement.Status challengeAchievementStatus = new ChallengeAchievement.Status(1L, challenge, true);
given(runningRecordRepository.findById(runningRecordId)).willReturn(Optional.of(runningRecord));
given(challengeAchievementRepository.findByRunningRecordId(runningRecordId))
.willReturn(Optional.of(challengeAchievementStatus));
+ given(challengeRepository.findChallengeWithConditionsByChallengeId(challenge.challengeId()))
+ .willReturn(Optional.of(new ChallengeWithCondition(
+ challenge,
+ List.of(new ChallengeCondition(
+ GoalMetricType.DISTANCE, ComparisonType.GREATER_THAN_OR_EQUAL_TO, 1_000)))));
// when
- RunningRecordQueryResponse result = runningRecordService.getRunningRecord(memberId, runningRecordId);
+ RunningResultDto result = runningRecordService.getRunningRecord(memberId, runningRecordId);
// then
- assertEquals(runningRecordId, result.runningRecordId());
- assertEquals(runningRecord.emoji(), result.emotion());
+ assertEquals(runningRecordId, result.runningRecord().runningId());
+ assertEquals(runningRecord.emoji(), result.runningRecord().emoji());
+ assertNotNull(result.challengeAchievement());
+ assertEquals(RunningAchievementMode.CHALLENGE, result.runningAchievementMode());
assertEquals(
challengeAchievementStatus.challenge().name(),
- result.challenge().title());
- assertEquals(RunningAchievementMode.CHALLENGE, result.achievementMode());
+ result.challengeAchievement().challenge().name());
+ assertEquals(1, result.percentage());
+ }
+
+ @Test
+ @DisplayName("Challenge 모드의 러닝 기록 조회 : DISTANCE_IN_TIME 타입일 경우 퍼센테이지 null 리턴 ")
+ void getRunningRecord_challenge_DISTANCE_IN_TIME_Type() {
+ // given
+ long memberId = 1;
+ long runningRecordId = 1;
+ Challenge challenge = new Challenge(1L, "challenge", "image", true, ChallengeType.DISTANCE_IN_TIME);
+ Member member = new Member(memberId, MemberRole.USER, "nickname1", OffsetDateTime.now(), OffsetDateTime.now());
+ RunningRecord runningRecord = new RunningRecord(
+ 1L,
+ member,
+ 10_000,
+ Duration.ofSeconds(10_000),
+ 500,
+ new Pace(5, 30),
+ ZonedDateTime.now(),
+ ZonedDateTime.now(),
+ List.of(new CoordinatePoint(0, 0, 0), new CoordinatePoint(0, 0, 0)),
+ "start location",
+ "end location",
+ RunningEmoji.VERY_GOOD);
+
+ ChallengeAchievement.Status challengeAchievementStatus = new ChallengeAchievement.Status(1L, challenge, true);
+
+ given(runningRecordRepository.findById(runningRecordId)).willReturn(Optional.of(runningRecord));
+ given(challengeAchievementRepository.findByRunningRecordId(runningRecordId))
+ .willReturn(Optional.of(challengeAchievementStatus));
+ given(challengeRepository.findChallengeWithConditionsByChallengeId(challenge.challengeId()))
+ .willReturn(Optional.of(new ChallengeWithCondition(
+ challenge,
+ List.of(new ChallengeCondition(
+ GoalMetricType.DISTANCE, ComparisonType.GREATER_THAN_OR_EQUAL_TO, 1_000)))));
+
+ // when
+ RunningResultDto result = runningRecordService.getRunningRecord(memberId, runningRecordId);
+
+ // then
+ assertEquals(runningRecordId, result.runningRecord().runningId());
+ assertEquals(runningRecord.emoji(), result.runningRecord().emoji());
+ assertNotNull(result.challengeAchievement());
+ assertEquals(RunningAchievementMode.CHALLENGE, result.runningAchievementMode());
+ assertEquals(
+ challengeAchievementStatus.challenge().name(),
+ result.challengeAchievement().challenge().name());
+ assertNull(result.percentage());
+ }
+
+ @Test
+ @DisplayName("Challenge 모드의 러닝 기록 조회 : 퍼센테이지를 표시 할 수 없는 챌린지가 하나라도 있을 경우 퍼센테이지 null 리턴 ")
+ void getRunningRecord_challenge_hasNotPercentage() {
+ // given
+ long memberId = 1;
+ long runningRecordId = 1;
+ Challenge challenge = new Challenge(1L, "challenge", "image", true, ChallengeType.TODAY);
+ Member member = new Member(memberId, MemberRole.USER, "nickname1", OffsetDateTime.now(), OffsetDateTime.now());
+ RunningRecord runningRecord = new RunningRecord(
+ 1L,
+ member,
+ 10_000,
+ Duration.ofSeconds(10_000),
+ 500,
+ new Pace(5, 30),
+ ZonedDateTime.now(),
+ ZonedDateTime.now(),
+ List.of(new CoordinatePoint(0, 0, 0), new CoordinatePoint(0, 0, 0)),
+ "start location",
+ "end location",
+ RunningEmoji.VERY_GOOD);
+
+ ChallengeAchievement.Status challengeAchievementStatus = new ChallengeAchievement.Status(1L, challenge, true);
+
+ given(runningRecordRepository.findById(runningRecordId)).willReturn(Optional.of(runningRecord));
+ given(challengeAchievementRepository.findByRunningRecordId(runningRecordId))
+ .willReturn(Optional.of(challengeAchievementStatus));
+ given(challengeRepository.findChallengeWithConditionsByChallengeId(challenge.challengeId()))
+ .willReturn(Optional.of(new ChallengeWithCondition(
+ challenge,
+ List.of(
+ new ChallengeCondition(
+ GoalMetricType.DISTANCE, ComparisonType.GREATER_THAN_OR_EQUAL_TO, 1_000),
+ new ChallengeCondition(
+ GoalMetricType.PACE, ComparisonType.GREATER_THAN_OR_EQUAL_TO, 1_000)))));
+
+ // when
+ RunningResultDto result = runningRecordService.getRunningRecord(memberId, runningRecordId);
+
+ // then
+ assertEquals(runningRecordId, result.runningRecord().runningId());
+ assertEquals(runningRecord.emoji(), result.runningRecord().emoji());
+ assertNotNull(result.challengeAchievement());
+ assertEquals(RunningAchievementMode.CHALLENGE, result.runningAchievementMode());
+ assertEquals(
+ challengeAchievementStatus.challenge().name(),
+ result.challengeAchievement().challenge().name());
+ assertNull(result.percentage());
}
@Test
@@ -531,10 +635,25 @@ private RunningRecord createRunningRecord(RunningRecordRequestV1 request, Member
@DisplayName("러닝 결과 저장 V2")
class RunningRecordAddV2 {
+ private RunningRecordRequestV2.RunningRecordMetrics runningRecordMetrics;
+
+ @BeforeEach
+ void beforeEach() {
+ runningRecordMetrics = new RunningRecordRequestV2.RunningRecordMetrics(
+ Duration.ofSeconds(600),
+ 1000,
+ 500.0,
+ List.of(
+ new RouteDtoV2(new RouteDtoV2.Point(0, 0), new RouteDtoV2.Point(1, 1)),
+ new RouteDtoV2(new RouteDtoV2.Point(2, 2), new RouteDtoV2.Point(3, 3)),
+ new RouteDtoV2(new RouteDtoV2.Point(4, 4), new RouteDtoV2.Point(5, 5))));
+ }
+
@Test
@DisplayName("러닝 결과 저장 : 루트가 순서대로 들어갔는지 확인")
void addRunningRecordV2_CheckRoute() {
// given
+ Member member = new Member(MemberRole.USER, "nickname1");
RunningRecordRequestV2 request = new RunningRecordRequestV2(
LocalDateTime.of(2021, 1, 1, 12, 10, 30),
LocalDateTime.of(2021, 1, 1, 13, 12, 10),
@@ -544,16 +663,251 @@ void addRunningRecordV2_CheckRoute() {
com.dnd.runus.presentation.v1.running.dto.request.RunningAchievementMode.NORMAL,
null,
null,
- new RunningRecordRequestV2.RunningRecordMetrics(
- Duration.ofSeconds(10_100),
- 10_000,
- 500.0,
- List.of(
- new RouteDtoV2(new RouteDtoV2.Point(0, 0), new RouteDtoV2.Point(1, 1)),
- new RouteDtoV2(new RouteDtoV2.Point(2, 2), new RouteDtoV2.Point(3, 3)),
- new RouteDtoV2(new RouteDtoV2.Point(4, 4), new RouteDtoV2.Point(5, 5)))));
-
- List route = request.runningData().route().stream()
+ runningRecordMetrics);
+
+ RunningRecord expectedRecord = createRecordFrom(member, request);
+
+ given(memberRepository.findById(member.memberId())).willReturn(Optional.of(member));
+ given(runningRecordRepository.save(expectedRecord)).willReturn(expectedRecord);
+
+ // when
+ RunningResultDto response = runningRecordService.addRunningRecordV2(member.memberId(), request);
+
+ // then
+ assertEquals(
+ com.dnd.runus.presentation.v1.running.dto.request.RunningAchievementMode.NORMAL,
+ response.runningAchievementMode());
+ assertNull(response.challengeAchievement());
+ assertNull(response.goalAchievement());
+ assertNull(response.percentage());
+
+ RunningRecord resultRunning = response.runningRecord();
+ for (int i = 0; i < resultRunning.route().size(); i++) {
+ assertEquals(i, resultRunning.route().get(i).longitude());
+ assertEquals(i, resultRunning.route().get(i).latitude());
+ }
+ }
+
+ @Test
+ @DisplayName("러닝 모드가가 챌린지(어제보다 ~) : 기준이 되는 러닝 기록의 어제 러닝 기록이 없으면 NotFoundException를 발생한다.")
+ void addRunningRecordV2_ChallengeMode_DefeatYesterday_RunningRecordNotFoundError() {
+ // given
+ Member member = new Member(MemberRole.USER, "nickname1");
+ Challenge challenge =
+ new Challenge(1L, "어제보다 1km 더 달리기", 360, "image url", true, ChallengeType.DEFEAT_YESTERDAY);
+ RunningRecordRequestV2 request = new RunningRecordRequestV2(
+ LocalDateTime.of(2021, 1, 1, 12, 10, 30),
+ LocalDateTime.of(2021, 1, 1, 13, 12, 10),
+ "start location",
+ "end location",
+ RunningEmoji.VERY_GOOD,
+ com.dnd.runus.presentation.v1.running.dto.request.RunningAchievementMode.CHALLENGE,
+ new RunningRecordRequestV2.ChallengeAchievedDto(challenge.challengeId(), true),
+ null,
+ runningRecordMetrics);
+ RunningRecord expectedRecord = createRecordFrom(member, request);
+ ChallengeAchievement expectedChallengeAchievement = new ChallengeAchievement(
+ challenge, expectedRecord, request.challengeValues().isSuccess());
+ given(memberRepository.findById(member.memberId())).willReturn(Optional.of(member));
+ given(runningRecordRepository.save(expectedRecord)).willReturn(expectedRecord);
+ given(challengeRepository.findById(request.challengeValues().challengeId()))
+ .willReturn(Optional.of(challenge));
+ given(challengeAchievementRepository.save(expectedChallengeAchievement))
+ .willReturn(expectedChallengeAchievement);
+ given(challengeRepository.findChallengeWithConditionsByChallengeId(challenge.challengeId()))
+ .willReturn(Optional.of(new ChallengeWithCondition(
+ challenge,
+ List.of(new ChallengeCondition(
+ GoalMetricType.DISTANCE, ComparisonType.GREATER_THAN_OR_EQUAL_TO, 1000)))));
+ OffsetDateTime runningDate = expectedRecord
+ .startAt()
+ .toLocalDate()
+ .atStartOfDay(expectedRecord.startAt().getZone())
+ .toOffsetDateTime();
+ given(runningRecordRepository.findByMemberIdAndStartAtBetween(
+ member.memberId(), runningDate.minusDays(1), runningDate))
+ .willReturn(List.of());
+
+ // when, then
+ assertThrows(
+ NotFoundException.class, () -> runningRecordService.addRunningRecordV2(member.memberId(), request));
+ }
+
+ @Test
+ @DisplayName("러닝 모드가 챌린지(어제보다 ~km더 뛰기) : 챌린지 관련 값이 정상적으로 저장되었는지 확인한다.")
+ void addRunningRecordV2_ChallengeMode_DefeatYesterday_Distance() {
+ // given
+ Member member = new Member(MemberRole.USER, "nickname1");
+ Challenge challenge =
+ new Challenge(1L, "어제보다 1km 더 달리기", 360, "image url", true, ChallengeType.DEFEAT_YESTERDAY);
+ RunningRecordRequestV2 request = new RunningRecordRequestV2(
+ LocalDateTime.of(2021, 1, 1, 12, 10, 30),
+ LocalDateTime.of(2021, 1, 1, 13, 12, 10),
+ "start location",
+ "end location",
+ RunningEmoji.VERY_GOOD,
+ com.dnd.runus.presentation.v1.running.dto.request.RunningAchievementMode.CHALLENGE,
+ new RunningRecordRequestV2.ChallengeAchievedDto(challenge.challengeId(), false),
+ null,
+ runningRecordMetrics);
+ RunningRecord expectedRecord = createRecordFrom(member, request);
+ ChallengeAchievement expectedChallengeAchievement = new ChallengeAchievement(
+ challenge, expectedRecord, request.challengeValues().isSuccess());
+ OffsetDateTime runningDate = expectedRecord
+ .startAt()
+ .toLocalDate()
+ .atStartOfDay(expectedRecord.startAt().getZone())
+ .toOffsetDateTime();
+ RunningRecord yesterdayRunning = RunningRecord.builder()
+ .member(member)
+ .startAt(expectedRecord.startAt().minusDays(1))
+ .endAt(expectedRecord.endAt().minusDays(1))
+ .emoji(expectedRecord.emoji())
+ .startLocation(expectedRecord.startLocation())
+ .endLocation(expectedRecord.endLocation())
+ .distanceMeter(expectedRecord.distanceMeter())
+ .duration(expectedRecord.duration())
+ .calorie(expectedRecord.calorie())
+ .averagePace(expectedRecord.averagePace())
+ .route(expectedRecord.route())
+ .build();
+ given(memberRepository.findById(member.memberId())).willReturn(Optional.of(member));
+ given(runningRecordRepository.save(expectedRecord)).willReturn(expectedRecord);
+ given(challengeRepository.findById(request.challengeValues().challengeId()))
+ .willReturn(Optional.of(challenge));
+ given(challengeAchievementRepository.save(expectedChallengeAchievement))
+ .willReturn(expectedChallengeAchievement);
+ given(challengeRepository.findChallengeWithConditionsByChallengeId(challenge.challengeId()))
+ .willReturn(Optional.of(new ChallengeWithCondition(
+ challenge,
+ List.of(new ChallengeCondition(
+ GoalMetricType.DISTANCE, ComparisonType.GREATER_THAN_OR_EQUAL_TO, 1000)))));
+ given(runningRecordRepository.findByMemberIdAndStartAtBetween(
+ member.memberId(), runningDate.minusDays(1), runningDate))
+ .willReturn(List.of(yesterdayRunning));
+
+ // when
+ RunningResultDto result = runningRecordService.addRunningRecordV2(member.memberId(), request);
+
+ // then
+ assertEquals(
+ result.runningAchievementMode(),
+ com.dnd.runus.presentation.v1.running.dto.request.RunningAchievementMode.CHALLENGE);
+ assertNotNull(result.challengeAchievement());
+ assertNotNull(result.percentage());
+ assertEquals(0.5, result.percentage());
+ }
+
+ @Test
+ @DisplayName("러닝 모드가가 챌린지(어제보다 ~분 더 뛰기) : 챌린지 관련 값이 정상적으로 저장되었는지 확인한다.")
+ void addRunningRecordV2_ChallengeMode_DefeatYesterday_Time() {
+ // given
+ Member member = new Member(MemberRole.USER, "nickname1");
+ Challenge challenge =
+ new Challenge(1L, "어제보다 30분 더 달리기", 360, "image url", true, ChallengeType.DEFEAT_YESTERDAY);
+ RunningRecordRequestV2 request = new RunningRecordRequestV2(
+ LocalDateTime.of(2021, 1, 1, 12, 10, 30),
+ LocalDateTime.of(2021, 1, 1, 13, 12, 10),
+ "start location",
+ "end location",
+ RunningEmoji.VERY_GOOD,
+ com.dnd.runus.presentation.v1.running.dto.request.RunningAchievementMode.CHALLENGE,
+ new RunningRecordRequestV2.ChallengeAchievedDto(challenge.challengeId(), false),
+ null,
+ runningRecordMetrics);
+ RunningRecord expectedRecord = createRecordFrom(member, request);
+ ChallengeAchievement expectedChallengeAchievement = new ChallengeAchievement(
+ challenge, expectedRecord, request.challengeValues().isSuccess());
+ OffsetDateTime runningDate = expectedRecord
+ .startAt()
+ .toLocalDate()
+ .atStartOfDay(expectedRecord.startAt().getZone())
+ .toOffsetDateTime();
+ RunningRecord yesterdayRunning = RunningRecord.builder()
+ .member(member)
+ .startAt(expectedRecord.startAt().minusDays(1))
+ .endAt(expectedRecord.endAt().minusDays(1))
+ .emoji(expectedRecord.emoji())
+ .startLocation(expectedRecord.startLocation())
+ .endLocation(expectedRecord.endLocation())
+ .distanceMeter(expectedRecord.distanceMeter())
+ .duration(expectedRecord.duration())
+ .calorie(expectedRecord.calorie())
+ .averagePace(expectedRecord.averagePace())
+ .route(expectedRecord.route())
+ .build();
+ given(memberRepository.findById(member.memberId())).willReturn(Optional.of(member));
+ given(runningRecordRepository.save(expectedRecord)).willReturn(expectedRecord);
+ given(challengeRepository.findById(request.challengeValues().challengeId()))
+ .willReturn(Optional.of(challenge));
+ given(challengeAchievementRepository.save(expectedChallengeAchievement))
+ .willReturn(expectedChallengeAchievement);
+ given(challengeRepository.findChallengeWithConditionsByChallengeId(challenge.challengeId()))
+ .willReturn(Optional.of(new ChallengeWithCondition(
+ challenge,
+ List.of(new ChallengeCondition(
+ GoalMetricType.TIME, ComparisonType.GREATER_THAN_OR_EQUAL_TO, 1800)))));
+ given(runningRecordRepository.findByMemberIdAndStartAtBetween(
+ member.memberId(), runningDate.minusDays(1), runningDate))
+ .willReturn(List.of(yesterdayRunning));
+
+ // when
+ RunningResultDto result = runningRecordService.addRunningRecordV2(member.memberId(), request);
+
+ // then
+ assertEquals(
+ result.runningAchievementMode(),
+ com.dnd.runus.presentation.v1.running.dto.request.RunningAchievementMode.CHALLENGE);
+ assertNotNull(result.challengeAchievement());
+ assertNotNull(result.percentage());
+ assertEquals(0.25, result.percentage());
+ }
+
+ @Test
+ @DisplayName("러닝 모드가가 챌린지이고 챌린지가 성공일 경우 퍼센티이지 값은 1를 리턴한다.")
+ void addRunningRecordV2_ChallengeMode_CheckPercentage() {
+ // given
+ Member member = new Member(MemberRole.USER, "nickname1");
+ Challenge challenge = new Challenge(1L, "500m 달리기", 360, "image url", true, ChallengeType.TODAY);
+ RunningRecordRequestV2 request = new RunningRecordRequestV2(
+ LocalDateTime.of(2021, 1, 1, 12, 10, 30),
+ LocalDateTime.of(2021, 1, 1, 13, 12, 10),
+ "start location",
+ "end location",
+ RunningEmoji.VERY_GOOD,
+ com.dnd.runus.presentation.v1.running.dto.request.RunningAchievementMode.CHALLENGE,
+ new RunningRecordRequestV2.ChallengeAchievedDto(challenge.challengeId(), true),
+ null,
+ runningRecordMetrics);
+ RunningRecord expectedRecord = createRecordFrom(member, request);
+ ChallengeAchievement expectedChallengeAchievement = new ChallengeAchievement(
+ challenge, expectedRecord, request.challengeValues().isSuccess());
+ given(memberRepository.findById(member.memberId())).willReturn(Optional.of(member));
+ given(runningRecordRepository.save(expectedRecord)).willReturn(expectedRecord);
+ given(challengeRepository.findById(request.challengeValues().challengeId()))
+ .willReturn(Optional.of(challenge));
+ given(challengeAchievementRepository.save(expectedChallengeAchievement))
+ .willReturn(expectedChallengeAchievement);
+ given(challengeRepository.findChallengeWithConditionsByChallengeId(challenge.challengeId()))
+ .willReturn(Optional.of(new ChallengeWithCondition(
+ challenge,
+ List.of(new ChallengeCondition(
+ GoalMetricType.DISTANCE, ComparisonType.GREATER_THAN_OR_EQUAL_TO, 500)))));
+
+ // when
+ RunningResultDto result = runningRecordService.addRunningRecordV2(member.memberId(), request);
+
+ // then
+ assertEquals(
+ result.runningAchievementMode(),
+ com.dnd.runus.presentation.v1.running.dto.request.RunningAchievementMode.CHALLENGE);
+ assertNotNull(result.challengeAchievement());
+ assertNotNull(result.percentage());
+ assertEquals(1, result.percentage());
+ }
+
+ private RunningRecord createRecordFrom(Member member, RunningRecordRequestV2 request) {
+ List coordinatePoints = request.runningData().route().stream()
.flatMap(point -> Stream.of(
new CoordinatePoint(
point.start().longitude(), point.start().latitude()),
@@ -561,9 +915,7 @@ void addRunningRecordV2_CheckRoute() {
point.end().longitude(), point.end().latitude())))
.collect(Collectors.toList());
- Member member = new Member(MemberRole.USER, "nickname1");
-
- RunningRecord expectedRecord = RunningRecord.builder()
+ return RunningRecord.builder()
.member(member)
.startAt(request.startAt().atZone(defaultZoneOffset))
.endAt(request.endAt().atZone(defaultZoneOffset))
@@ -576,27 +928,8 @@ void addRunningRecordV2_CheckRoute() {
.averagePace(Pace.from(
request.runningData().distanceMeter(),
request.runningData().runningTime()))
- .route(route)
+ .route(coordinatePoints)
.build();
-
- given(memberRepository.findById(1L)).willReturn(Optional.of(member));
- given(runningRecordRepository.save(expectedRecord)).willReturn(expectedRecord);
-
- // when
- RunningResultDto response = runningRecordService.addRunningRecordV2(1L, request);
-
- // then
- assertEquals(
- com.dnd.runus.presentation.v1.running.dto.request.RunningAchievementMode.NORMAL,
- response.runningAchievementMode());
- assertNull(response.challengeAchievement());
- assertNull(response.goalAchievement());
-
- RunningRecord resultRunning = response.runningRecord();
- for (int i = 0; i < resultRunning.route().size(); i++) {
- assertEquals(i, resultRunning.route().get(i).longitude());
- assertEquals(i, resultRunning.route().get(i).latitude());
- }
}
}
}
diff --git a/src/test/java/com/dnd/runus/presentation/v2/running/RunningRecordControllerV2Test.java b/src/test/java/com/dnd/runus/presentation/v2/running/RunningRecordControllerV2Test.java
index 7dc00cd8..9bda3117 100644
--- a/src/test/java/com/dnd/runus/presentation/v2/running/RunningRecordControllerV2Test.java
+++ b/src/test/java/com/dnd/runus/presentation/v2/running/RunningRecordControllerV2Test.java
@@ -110,6 +110,7 @@ void addRunningRecord_Normal_CheckRunningPath() throws Exception {
createRunningRecordFrom(request),
com.dnd.runus.presentation.v1.running.dto.request.RunningAchievementMode.NORMAL,
null,
+ null,
null));
// when
@@ -121,8 +122,7 @@ void addRunningRecord_Normal_CheckRunningPath() throws Exception {
result.andExpect(status().isOk())
.andExpect(jsonPath("$.data.emotion").value("very-good"))
.andExpect(jsonPath("$.data.achievementMode").value("normal"))
- .andExpect(jsonPath("$.data.challenge").doesNotExist())
- .andExpect(jsonPath("$.data.goal").doesNotExist())
+ .andExpect(jsonPath("$.data.achievementResult").doesNotExist())
.andExpect(jsonPath("$.data.runningData.averagePace").value("5’30”"))
.andExpect(
jsonPath("$.data.runningData.route[0].start.longitude").value("1.0"))