From 428dcf1afb86b80d2651c3d35b53170d7e89c327 Mon Sep 17 00:00:00 2001 From: Develop-KIM Date: Wed, 8 Jul 2026 21:22:35 +0900 Subject: [PATCH] =?UTF-8?q?feat(achievement):=20=EC=B5=9C=EA=B3=A0=20?= =?UTF-8?q?=EB=A0=88=EB=B2=A8(=EB=AF=B8=EC=85=98=20=EC=97=86=EC=9D=8C)=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20=EC=A7=80=EC=9B=90=20=EB=B0=8F=20=EC=9D=B4?= =?UTF-8?q?=EB=B2=88=20=EC=A3=BC=20=ED=9A=8C=EA=B3=A0=20=ED=98=84=ED=99=A9?= =?UTF-8?q?=20=EB=85=B8=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CurrentMissionResponse.mission 을 nullable 로 변경해 최고 레벨에서 미션 없이 응답 - 최고 레벨 분기에서도 이번 주 회고 현황(weeklyStatus) 계산해 반환 - confirmLevelUp: 진행 중 미션이 없을 때 최근 미션으로 폴백 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../achievement/MissionQueryService.kt | 20 ++++++++-- .../achievement/UserMissionService.kt | 1 + .../achievement/dto/CurrentMissionResponse.kt | 2 +- .../achievement/MissionQueryServiceTest.kt | 37 +++++++++++-------- 4 files changed, 40 insertions(+), 20 deletions(-) diff --git a/src/main/kotlin/com/didit/application/achievement/MissionQueryService.kt b/src/main/kotlin/com/didit/application/achievement/MissionQueryService.kt index 8556caf4..93670291 100644 --- a/src/main/kotlin/com/didit/application/achievement/MissionQueryService.kt +++ b/src/main/kotlin/com/didit/application/achievement/MissionQueryService.kt @@ -5,7 +5,6 @@ import com.didit.application.achievement.dto.MissionInfo import com.didit.application.achievement.dto.PopupStatus import com.didit.application.achievement.dto.WeeklyStatus import com.didit.application.achievement.exception.CurrentMissionNotFoundException -import com.didit.application.achievement.exception.MissionDefinitionNotFoundException import com.didit.application.achievement.exception.UserLevelNotFoundException import com.didit.application.achievement.provided.MissionFinder import com.didit.application.achievement.required.MissionRepository @@ -34,11 +33,21 @@ class MissionQueryService( ) : MissionFinder { override fun getCurrentMission(userId: UUID): CurrentMissionResponse { val userLevel = userLevelRepository.findByUserId(userId) ?: throw UserLevelNotFoundException(userId) + val missionLevel = userLevel.currentLevel + 1 + val mission = missionRepository.findByLevel(missionLevel) + + if (mission == null) { + val lastMission = userMissionRepository.findByUserId(userId).firstOrNull() + return CurrentMissionResponse( + currentLevel = userLevel.currentLevel, + mission = null, + weeklyStatus = buildWeeklyStatusForUser(userId), + popup = lastMission?.let { getPopupStatus(it) } ?: PopupStatus(exists = false, type = null), + ) + } + val userMission = userMissionRepository.findCurrentMissionByUserId(userId) ?: throw CurrentMissionNotFoundException(userId) - val missionLevel = userLevel.currentLevel + 1 - val mission = - missionRepository.findByLevel(missionLevel) ?: throw MissionDefinitionNotFoundException(missionLevel) val missionInfo = MissionInfo( @@ -86,7 +95,10 @@ class MissionQueryService( if (mission.missionType != MissionType.CONSECUTIVE_WEEK) { return null } + return buildWeeklyStatusForUser(userId) + } + private fun buildWeeklyStatusForUser(userId: UUID): WeeklyStatus { val weekStartDate = getWeekStartDate(ServiceTime.today()) val from = ServiceTime.startOfDayUtc(weekStartDate) val to = ServiceTime.startOfDayUtc(weekStartDate.plusWeeks(1)) diff --git a/src/main/kotlin/com/didit/application/achievement/UserMissionService.kt b/src/main/kotlin/com/didit/application/achievement/UserMissionService.kt index a5b433f8..bfdf919d 100644 --- a/src/main/kotlin/com/didit/application/achievement/UserMissionService.kt +++ b/src/main/kotlin/com/didit/application/achievement/UserMissionService.kt @@ -35,6 +35,7 @@ class UserMissionService( override fun confirmLevelUp(userId: UUID) { val userMission = userMissionRepository.findCurrentMissionByUserId(userId) + ?: userMissionRepository.findByUserId(userId).firstOrNull() ?: throw CurrentMissionNotFoundException(userId) userMission.levelUpPopupShown = true diff --git a/src/main/kotlin/com/didit/application/achievement/dto/CurrentMissionResponse.kt b/src/main/kotlin/com/didit/application/achievement/dto/CurrentMissionResponse.kt index d2e2b0d6..2a8ddbcd 100644 --- a/src/main/kotlin/com/didit/application/achievement/dto/CurrentMissionResponse.kt +++ b/src/main/kotlin/com/didit/application/achievement/dto/CurrentMissionResponse.kt @@ -2,7 +2,7 @@ package com.didit.application.achievement.dto data class CurrentMissionResponse( val currentLevel: Int, - val mission: MissionInfo, + val mission: MissionInfo?, val weeklyStatus: WeeklyStatus?, val popup: PopupStatus, ) diff --git a/src/test/kotlin/com/didit/application/achievement/MissionQueryServiceTest.kt b/src/test/kotlin/com/didit/application/achievement/MissionQueryServiceTest.kt index 2866ede5..97bd509b 100644 --- a/src/test/kotlin/com/didit/application/achievement/MissionQueryServiceTest.kt +++ b/src/test/kotlin/com/didit/application/achievement/MissionQueryServiceTest.kt @@ -1,7 +1,6 @@ package com.didit.application.achievement import com.didit.application.achievement.exception.CurrentMissionNotFoundException -import com.didit.application.achievement.exception.MissionDefinitionNotFoundException import com.didit.application.achievement.exception.UserLevelNotFoundException import com.didit.application.achievement.required.MissionRepository import com.didit.application.achievement.required.UserLevelRepository @@ -74,10 +73,10 @@ class MissionQueryServiceTest { val result = missionQueryService.getCurrentMission(userId) assertThat(result.currentLevel).isEqualTo(0) - assertThat(result.mission.type).isEqualTo("FIRST_RETRO") - assertThat(result.mission.progress).isEqualTo(0) - assertThat(result.mission.target).isEqualTo(1) - assertThat(result.mission.remainingDays).isNull() + assertThat(result.mission!!.type).isEqualTo("FIRST_RETRO") + assertThat(result.mission!!.progress).isEqualTo(0) + assertThat(result.mission!!.target).isEqualTo(1) + assertThat(result.mission!!.remainingDays).isNull() assertThat(result.weeklyStatus).isNull() assertThat(result.popup.exists).isFalse() } @@ -103,8 +102,8 @@ class MissionQueryServiceTest { val result = missionQueryService.getCurrentMission(userId) assertThat(result.currentLevel).isEqualTo(1) - assertThat(result.mission.type).isEqualTo("TIME_LIMITED") - assertThat(result.mission.remainingDays).isEqualTo(5) + assertThat(result.mission!!.type).isEqualTo("TIME_LIMITED") + assertThat(result.mission!!.remainingDays).isEqualTo(5) assertThat(result.weeklyStatus).isNull() } @@ -130,7 +129,7 @@ class MissionQueryServiceTest { val result = missionQueryService.getCurrentMission(userId) assertThat(result.currentLevel).isEqualTo(2) - assertThat(result.mission.type).isEqualTo("CONSECUTIVE_WEEK") + assertThat(result.mission!!.type).isEqualTo("CONSECUTIVE_WEEK") assertThat(result.weeklyStatus).isNotNull() assertThat(result.weeklyStatus!!.days).hasSize(7) } @@ -147,6 +146,7 @@ class MissionQueryServiceTest { fun `현재 미션이 없으면 CurrentMissionNotFoundException을 발생시킨다`() { val userLevel = UserLevel(userId = userId, currentLevel = 1) whenever(userLevelRepository.findByUserId(userId)).thenReturn(userLevel) + whenever(missionRepository.findByLevel(2)).thenReturn(Mission.timeLimited()) whenever(userMissionRepository.findCurrentMissionByUserId(userId)).thenReturn(null) assertThatThrownBy { missionQueryService.getCurrentMission(userId) } @@ -154,16 +154,23 @@ class MissionQueryServiceTest { } @Test - fun `미션 정의가 없으면 MissionDefinitionNotFoundException을 발생시킨다`() { - val userLevel = UserLevel(userId = userId, currentLevel = 0) - val userMission = UserMission(userId = userId, missionId = UUID.randomUUID()) + fun `최고 레벨(미션 정의 없음)이면 mission이 null이고 주간 현황을 반환한다`() { + val userLevel = UserLevel(userId = userId, currentLevel = 10) whenever(userLevelRepository.findByUserId(userId)).thenReturn(userLevel) - whenever(userMissionRepository.findCurrentMissionByUserId(userId)).thenReturn(userMission) - whenever(missionRepository.findByLevel(1)).thenReturn(null) + whenever(missionRepository.findByLevel(11)).thenReturn(null) + whenever(userMissionRepository.findByUserId(userId)).thenReturn(emptyList()) + whenever(retrospectiveRepository.findCompletedByUserIdAndPeriod(eq(userId), any(), any())).thenReturn( + emptyList(), + ) - assertThatThrownBy { missionQueryService.getCurrentMission(userId) } - .isInstanceOf(MissionDefinitionNotFoundException::class.java) + val result = missionQueryService.getCurrentMission(userId) + + assertThat(result.currentLevel).isEqualTo(10) + assertThat(result.mission).isNull() + assertThat(result.weeklyStatus).isNotNull() + assertThat(result.weeklyStatus!!.days).hasSize(7) + assertThat(result.popup.exists).isFalse() } @Test