From e5b2ff12e2d2a9a9054fa9dcfbc0b629ea83fd58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=99=A9=EC=A0=95=EB=B9=88?= Date: Mon, 6 Apr 2026 19:40:12 +0900 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20=EB=B0=B0=ED=8B=80=20=EC=A2=85?= =?UTF-8?q?=EB=A3=8C=20=EC=8B=9C=EA=B0=81=EC=9D=84=20=EC=88=98=EB=9D=BD=20?= =?UTF-8?q?=EC=8B=9C=EA=B0=81=20=EA=B8=B0=EC=A4=80=20N=EC=9D=BC=20?= =?UTF-8?q?=ED=9B=84=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/userexphistory/UserExpHistoryJpaRepository.java | 2 +- .../rival/battle/service/FindAllBattleInfoService.java | 2 +- .../battle/service/FindDetailedBattleInfoService.java | 8 ++++---- .../process/clash/domain/rival/battle/entity/Battle.java | 5 ++--- .../battle/service/FindAllBattleInfoServiceTest.java | 2 +- .../clash/domain/rival/battle/entity/BattleTest.java | 5 ++--- 6 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/process/clash/adapter/persistence/user/userexphistory/UserExpHistoryJpaRepository.java b/src/main/java/com/process/clash/adapter/persistence/user/userexphistory/UserExpHistoryJpaRepository.java index dd0634805..4d99a35a7 100644 --- a/src/main/java/com/process/clash/adapter/persistence/user/userexphistory/UserExpHistoryJpaRepository.java +++ b/src/main/java/com/process/clash/adapter/persistence/user/userexphistory/UserExpHistoryJpaRepository.java @@ -174,7 +174,7 @@ SELECT COALESCE(SUM(earn_exp), 0) JOIN battles b ON b.id IN (:battleIds) AND ueh.fk_user_id = :userId AND ueh.date >= (b.started_at AT TIME ZONE 'Asia/Seoul')::date - AND ueh.date < (b.end_at AT TIME ZONE 'Asia/Seoul')::date + AND ueh.date <= (b.end_at AT TIME ZONE 'Asia/Seoul')::date GROUP BY b.id """, nativeQuery = true) List findAverageExpForBattles( diff --git a/src/main/java/com/process/clash/application/compete/rival/battle/service/FindAllBattleInfoService.java b/src/main/java/com/process/clash/application/compete/rival/battle/service/FindAllBattleInfoService.java index 1e1428b43..ef3c72ea8 100644 --- a/src/main/java/com/process/clash/application/compete/rival/battle/service/FindAllBattleInfoService.java +++ b/src/main/java/com/process/clash/application/compete/rival/battle/service/FindAllBattleInfoService.java @@ -172,7 +172,7 @@ private BattleInfo toBattleInfo( ); LocalDate expireDate = battle.endAt() != null - ? battle.endAt().atZone(battleZoneId).toLocalDate().minusDays(1) + ? battle.endAt().atZone(battleZoneId).toLocalDate() : null; return BattleInfo.of( diff --git a/src/main/java/com/process/clash/application/compete/rival/battle/service/FindDetailedBattleInfoService.java b/src/main/java/com/process/clash/application/compete/rival/battle/service/FindDetailedBattleInfoService.java index c10188a4d..5881cca09 100644 --- a/src/main/java/com/process/clash/application/compete/rival/battle/service/FindDetailedBattleInfoService.java +++ b/src/main/java/com/process/clash/application/compete/rival/battle/service/FindDetailedBattleInfoService.java @@ -39,14 +39,14 @@ public FindDetailedBattleInfoData.Result execute(FindDetailedBattleInfoData.Comm ? battle.startedAt().atZone(battleZoneId).toLocalDate() : LocalDate.now(battleZoneId); - // exp 계산 기간 종료일: DONE이면 마지막 활동일(endAt 자정의 전날), 진행 중이면 오늘까지 + // exp 계산 기간 종료일: DONE이면 endAt 기준 날짜, 진행 중이면 오늘까지 LocalDate endDate = battle.battleStatus().equals(BattleStatus.DONE) && battle.endAt() != null - ? battle.endAt().atZone(battleZoneId).toLocalDate().minusDays(1) + ? battle.endAt().atZone(battleZoneId).toLocalDate() : LocalDate.now(battleZoneId); - // 클라이언트 표시용 종료 예정일: endAt 자정의 전날 = 마지막 활동일 + // 클라이언트 표시용 종료 예정일: endAt 기준 날짜 (마지막 활동일) LocalDate expireDate = battle.endAt() != null - ? battle.endAt().atZone(battleZoneId).toLocalDate().minusDays(1) + ? battle.endAt().atZone(battleZoneId).toLocalDate() : null; // 상대방이 탈퇴해 라이벌이 삭제된 경우 상대 정보 없이 반환 diff --git a/src/main/java/com/process/clash/domain/rival/battle/entity/Battle.java b/src/main/java/com/process/clash/domain/rival/battle/entity/Battle.java index 7d9cdb84f..02f727873 100644 --- a/src/main/java/com/process/clash/domain/rival/battle/entity/Battle.java +++ b/src/main/java/com/process/clash/domain/rival/battle/entity/Battle.java @@ -3,8 +3,8 @@ import com.process.clash.domain.rival.battle.enums.BattleStatus; import java.time.Instant; -import java.time.LocalDate; import java.time.ZoneId; +import java.time.temporal.ChronoUnit; public record Battle( Long id, @@ -36,8 +36,7 @@ public static Battle createDefault(int duration, Long rivalId, Long applicantId) } public Battle accept(Instant now, ZoneId zoneId) { - LocalDate acceptanceDateKST = now.atZone(zoneId).toLocalDate(); - Instant endAt = acceptanceDateKST.plusDays(this.duration).atStartOfDay(zoneId).toInstant(); + Instant endAt = now.plus(this.duration, ChronoUnit.DAYS); return new Battle( this.id, diff --git a/src/test/java/com/process/clash/application/compete/rival/battle/service/FindAllBattleInfoServiceTest.java b/src/test/java/com/process/clash/application/compete/rival/battle/service/FindAllBattleInfoServiceTest.java index 68e30c5c3..90daa9990 100644 --- a/src/test/java/com/process/clash/application/compete/rival/battle/service/FindAllBattleInfoServiceTest.java +++ b/src/test/java/com/process/clash/application/compete/rival/battle/service/FindAllBattleInfoServiceTest.java @@ -190,7 +190,7 @@ void execute_returnsEnemyTierAsRankTier_whenRankTierIsNotNone() { } @Test - @DisplayName("IN_PROGRESS 배틀의 expireDate는 endAt을 KST 기준 LocalDate로 변환한 값이다") + @DisplayName("IN_PROGRESS 배틀의 expireDate는 endAt의 KST 날짜(마지막 활동일)이다") void execute_returnsExpireDateFromEndAt_whenInProgress() { Instant endAt = Instant.now().plus(6, ChronoUnit.DAYS); Battle battle = new Battle(BATTLE_ID, Instant.now(), Instant.now(), diff --git a/src/test/java/com/process/clash/domain/rival/battle/entity/BattleTest.java b/src/test/java/com/process/clash/domain/rival/battle/entity/BattleTest.java index 875976a48..61ef6b7d1 100644 --- a/src/test/java/com/process/clash/domain/rival/battle/entity/BattleTest.java +++ b/src/test/java/com/process/clash/domain/rival/battle/entity/BattleTest.java @@ -14,7 +14,7 @@ class BattleTest { @Test - @DisplayName("승인 즉시 startedAt이 설정되고, endAt은 수락일(KST) + duration일의 자정 KST로 설정된다") + @DisplayName("승인 즉시 startedAt이 설정되고, endAt은 수락 시각으로부터 duration일(72시간 단위) 후로 설정된다") void accept_setsStartedAtAndReturnsInProgress() { Battle battle = pendingBattle(7); Instant now = Instant.now(); @@ -22,8 +22,7 @@ void accept_setsStartedAtAndReturnsInProgress() { Battle result = battle.accept(now, zoneId); - LocalDate acceptanceDateKST = now.atZone(zoneId).toLocalDate(); - Instant expectedEndAt = acceptanceDateKST.plusDays(7).atStartOfDay(zoneId).toInstant(); + Instant expectedEndAt = now.plus(7, ChronoUnit.DAYS); assertThat(result.battleStatus()).isEqualTo(BattleStatus.IN_PROGRESS); assertThat(result.startedAt()).isEqualTo(now); From eba20f6547c8c160d92a45059509279a16ec5a51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=99=A9=EC=A0=95=EB=B9=88?= Date: Tue, 7 Apr 2026 08:42:06 +0900 Subject: [PATCH 2/2] =?UTF-8?q?refactor:=20=EC=BD=94=EB=93=9C=20=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EB=B0=98=EC=98=81=20-=20accept()=20zoneId=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20=EC=9E=90=EC=A0=95=20=EC=97=A3?= =?UTF-8?q?=EC=A7=80=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../userexphistory/UserExpHistoryJpaRepository.java | 2 +- .../rival/battle/service/AcceptBattleService.java | 5 +---- .../battle/service/FindAllBattleInfoService.java | 2 +- .../service/FindDetailedBattleInfoService.java | 4 ++-- .../clash/domain/rival/battle/entity/Battle.java | 3 +-- .../battle/service/AcceptBattleServiceTest.java | 12 ++++-------- .../battle/service/FindAllBattleInfoServiceTest.java | 2 +- .../clash/domain/rival/battle/entity/BattleTest.java | 5 +---- 8 files changed, 12 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/process/clash/adapter/persistence/user/userexphistory/UserExpHistoryJpaRepository.java b/src/main/java/com/process/clash/adapter/persistence/user/userexphistory/UserExpHistoryJpaRepository.java index 4d99a35a7..428aeda72 100644 --- a/src/main/java/com/process/clash/adapter/persistence/user/userexphistory/UserExpHistoryJpaRepository.java +++ b/src/main/java/com/process/clash/adapter/persistence/user/userexphistory/UserExpHistoryJpaRepository.java @@ -174,7 +174,7 @@ SELECT COALESCE(SUM(earn_exp), 0) JOIN battles b ON b.id IN (:battleIds) AND ueh.fk_user_id = :userId AND ueh.date >= (b.started_at AT TIME ZONE 'Asia/Seoul')::date - AND ueh.date <= (b.end_at AT TIME ZONE 'Asia/Seoul')::date + AND ueh.date <= ((b.end_at - interval '1 microsecond') AT TIME ZONE 'Asia/Seoul')::date GROUP BY b.id """, nativeQuery = true) List findAverageExpForBattles( diff --git a/src/main/java/com/process/clash/application/compete/rival/battle/service/AcceptBattleService.java b/src/main/java/com/process/clash/application/compete/rival/battle/service/AcceptBattleService.java index 8919e7b5a..b4995226d 100644 --- a/src/main/java/com/process/clash/application/compete/rival/battle/service/AcceptBattleService.java +++ b/src/main/java/com/process/clash/application/compete/rival/battle/service/AcceptBattleService.java @@ -16,7 +16,6 @@ import org.springframework.transaction.annotation.Transactional; import java.time.Instant; -import java.time.ZoneId; import java.util.List; @Service @@ -29,7 +28,6 @@ public class AcceptBattleService implements AcceptBattleUseCase { private final RivalRepositoryPort rivalRepositoryPort; private final UserNoticeRepositoryPort userNoticeRepositoryPort; private final CompeteRefetchNotifier competeRefetchNotifier; - private final ZoneId battleZoneId; @Override public void execute(ModifyBattleData.Command command) { @@ -39,8 +37,7 @@ public void execute(ModifyBattleData.Command command) { Battle battle = battleRepositoryPort.findById(command.id()) .orElseThrow(BattleNotFoundException::new); - // 승인 시각을 배틀 시작 시각으로 사용, 종료 시각은 수락일(KST) + duration일의 자정 KST - Battle updatedBattle = battle.accept(Instant.now(), battleZoneId); + Battle updatedBattle = battle.accept(Instant.now()); battleRepositoryPort.save(updatedBattle); diff --git a/src/main/java/com/process/clash/application/compete/rival/battle/service/FindAllBattleInfoService.java b/src/main/java/com/process/clash/application/compete/rival/battle/service/FindAllBattleInfoService.java index ef3c72ea8..04e70834c 100644 --- a/src/main/java/com/process/clash/application/compete/rival/battle/service/FindAllBattleInfoService.java +++ b/src/main/java/com/process/clash/application/compete/rival/battle/service/FindAllBattleInfoService.java @@ -172,7 +172,7 @@ private BattleInfo toBattleInfo( ); LocalDate expireDate = battle.endAt() != null - ? battle.endAt().atZone(battleZoneId).toLocalDate() + ? battle.endAt().minusNanos(1).atZone(battleZoneId).toLocalDate() : null; return BattleInfo.of( diff --git a/src/main/java/com/process/clash/application/compete/rival/battle/service/FindDetailedBattleInfoService.java b/src/main/java/com/process/clash/application/compete/rival/battle/service/FindDetailedBattleInfoService.java index 5881cca09..22a105b43 100644 --- a/src/main/java/com/process/clash/application/compete/rival/battle/service/FindDetailedBattleInfoService.java +++ b/src/main/java/com/process/clash/application/compete/rival/battle/service/FindDetailedBattleInfoService.java @@ -41,12 +41,12 @@ public FindDetailedBattleInfoData.Result execute(FindDetailedBattleInfoData.Comm // exp 계산 기간 종료일: DONE이면 endAt 기준 날짜, 진행 중이면 오늘까지 LocalDate endDate = battle.battleStatus().equals(BattleStatus.DONE) && battle.endAt() != null - ? battle.endAt().atZone(battleZoneId).toLocalDate() + ? battle.endAt().minusNanos(1).atZone(battleZoneId).toLocalDate() : LocalDate.now(battleZoneId); // 클라이언트 표시용 종료 예정일: endAt 기준 날짜 (마지막 활동일) LocalDate expireDate = battle.endAt() != null - ? battle.endAt().atZone(battleZoneId).toLocalDate() + ? battle.endAt().minusNanos(1).atZone(battleZoneId).toLocalDate() : null; // 상대방이 탈퇴해 라이벌이 삭제된 경우 상대 정보 없이 반환 diff --git a/src/main/java/com/process/clash/domain/rival/battle/entity/Battle.java b/src/main/java/com/process/clash/domain/rival/battle/entity/Battle.java index 02f727873..08f7aa121 100644 --- a/src/main/java/com/process/clash/domain/rival/battle/entity/Battle.java +++ b/src/main/java/com/process/clash/domain/rival/battle/entity/Battle.java @@ -3,7 +3,6 @@ import com.process.clash.domain.rival.battle.enums.BattleStatus; import java.time.Instant; -import java.time.ZoneId; import java.time.temporal.ChronoUnit; public record Battle( @@ -35,7 +34,7 @@ public static Battle createDefault(int duration, Long rivalId, Long applicantId) ); } - public Battle accept(Instant now, ZoneId zoneId) { + public Battle accept(Instant now) { Instant endAt = now.plus(this.duration, ChronoUnit.DAYS); return new Battle( diff --git a/src/test/java/com/process/clash/application/compete/rival/battle/service/AcceptBattleServiceTest.java b/src/test/java/com/process/clash/application/compete/rival/battle/service/AcceptBattleServiceTest.java index 884e3392f..4224ad3e5 100644 --- a/src/test/java/com/process/clash/application/compete/rival/battle/service/AcceptBattleServiceTest.java +++ b/src/test/java/com/process/clash/application/compete/rival/battle/service/AcceptBattleServiceTest.java @@ -19,7 +19,6 @@ import org.mockito.junit.jupiter.MockitoExtension; import java.time.Instant; -import java.time.ZoneId; import java.util.Collection; import java.util.Optional; @@ -47,8 +46,6 @@ class AcceptBattleServiceTest { private AcceptBattleService acceptBattleService; - private static final ZoneId BATTLE_ZONE_ID = ZoneId.of("Asia/Seoul"); - @BeforeEach void setUp() { acceptBattleService = new AcceptBattleService( @@ -56,8 +53,7 @@ void setUp() { modifyBattlePolicy, rivalRepositoryPort, userNoticeRepositoryPort, - competeRefetchNotifier, - BATTLE_ZONE_ID + competeRefetchNotifier ); } @@ -71,7 +67,7 @@ void execute_notifiesBothUsersOnAccept() { Battle battle = pendingBattle(battleId, rivalId, opponentId); when(battleRepositoryPort.findById(battleId)).thenReturn(Optional.of(battle)); - when(battleRepositoryPort.save(any(Battle.class))).thenReturn(battle.accept(Instant.now(), BATTLE_ZONE_ID)); + when(battleRepositoryPort.save(any(Battle.class))).thenReturn(battle.accept(Instant.now())); when(rivalRepositoryPort.findOpponentIdByIdAndUserId(rivalId, actor.id())).thenReturn(opponentId); when(userNoticeRepositoryPort.save(any(UserNotice.class))).thenAnswer(invocation -> invocation.getArgument(0)); @@ -96,7 +92,7 @@ void execute_savesOneNoticeOnAccept() { Battle battle = pendingBattle(battleId, rivalId, opponentId); when(battleRepositoryPort.findById(battleId)).thenReturn(Optional.of(battle)); - when(battleRepositoryPort.save(any(Battle.class))).thenReturn(battle.accept(Instant.now(), BATTLE_ZONE_ID)); + when(battleRepositoryPort.save(any(Battle.class))).thenReturn(battle.accept(Instant.now())); when(rivalRepositoryPort.findOpponentIdByIdAndUserId(rivalId, actor.id())).thenReturn(opponentId); when(userNoticeRepositoryPort.save(any(UserNotice.class))).thenAnswer(invocation -> invocation.getArgument(0)); @@ -115,7 +111,7 @@ void execute_softDeletesApplyBattleNoticeOnAccept() { Battle battle = pendingBattle(battleId, rivalId, opponentId); when(battleRepositoryPort.findById(battleId)).thenReturn(Optional.of(battle)); - when(battleRepositoryPort.save(any(Battle.class))).thenReturn(battle.accept(Instant.now(), BATTLE_ZONE_ID)); + when(battleRepositoryPort.save(any(Battle.class))).thenReturn(battle.accept(Instant.now())); when(rivalRepositoryPort.findOpponentIdByIdAndUserId(rivalId, actor.id())).thenReturn(opponentId); when(userNoticeRepositoryPort.save(any(UserNotice.class))).thenAnswer(invocation -> invocation.getArgument(0)); diff --git a/src/test/java/com/process/clash/application/compete/rival/battle/service/FindAllBattleInfoServiceTest.java b/src/test/java/com/process/clash/application/compete/rival/battle/service/FindAllBattleInfoServiceTest.java index 90daa9990..e8016a8b7 100644 --- a/src/test/java/com/process/clash/application/compete/rival/battle/service/FindAllBattleInfoServiceTest.java +++ b/src/test/java/com/process/clash/application/compete/rival/battle/service/FindAllBattleInfoServiceTest.java @@ -205,7 +205,7 @@ void execute_returnsExpireDateFromEndAt_whenInProgress() { FindAllBattleInfoData.Result result = findAllBattleInfoService.execute(command()); - LocalDate expectedDate = endAt.atZone(ZoneId.of("Asia/Seoul")).toLocalDate(); + LocalDate expectedDate = endAt.minusNanos(1).atZone(ZoneId.of("Asia/Seoul")).toLocalDate(); assertThat(result.battles().get(0).expireDate()).isEqualTo(expectedDate); } diff --git a/src/test/java/com/process/clash/domain/rival/battle/entity/BattleTest.java b/src/test/java/com/process/clash/domain/rival/battle/entity/BattleTest.java index 61ef6b7d1..f65e5f13a 100644 --- a/src/test/java/com/process/clash/domain/rival/battle/entity/BattleTest.java +++ b/src/test/java/com/process/clash/domain/rival/battle/entity/BattleTest.java @@ -5,8 +5,6 @@ import org.junit.jupiter.api.Test; import java.time.Instant; -import java.time.LocalDate; -import java.time.ZoneId; import java.time.temporal.ChronoUnit; import static org.assertj.core.api.Assertions.assertThat; @@ -18,9 +16,8 @@ class BattleTest { void accept_setsStartedAtAndReturnsInProgress() { Battle battle = pendingBattle(7); Instant now = Instant.now(); - ZoneId zoneId = ZoneId.of("Asia/Seoul"); - Battle result = battle.accept(now, zoneId); + Battle result = battle.accept(now); Instant expectedEndAt = now.plus(7, ChronoUnit.DAYS);