From 48d9e8b3b181df559bc114130d73667bd014157f Mon Sep 17 00:00:00 2001 From: "Choi, Minwoo" Date: Thu, 18 Dec 2025 22:36:04 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20StudyLogDailyMission,=20DailyMissio?= =?UTF-8?q?n,=20Mission=20=EA=B8=B0=EB=8A=A5=20=EC=A0=84=EB=B0=98=20Kotlin?= =?UTF-8?q?=20=EB=A7=88=EC=9D=B4=EA=B7=B8=EB=A0=88=EC=9D=B4=EC=85=98(#122)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: StudyLogDailyMissionQueryService.kt, StudyLogDailyMissionCommandService.kt 구현 * feat: StudyLogDailyMissionRepository.kt, StudyLogDailyMissionJpaRepository.kt, StudyLogDailyMissionRepositoryAdapter.kt 구현 * feat: StudyLogDailyMissionFactory.kt 구현 * feat: DailyMissionQueryService.kt, DailyMissionCommandService.kt 구현 * feat: DailyMissionRepository.kt, DailyMissionJpaRepository.kt, DailyMissionRepositoryAdapter.kt 구현 * feat: DailyMissionErrorCode.kt, DailyMissionPolicy.kt 구현 * feat: DailyMissionFactory 구현 * feat: MissionController.kt 구현 * feat: MissionFacade.kt 구현 * feat: MissionQueryService.kt, MissionCommandService.kt 구현 * feat: MissionRepository.kt, MissionJpaRepository.kt, MissionRepositoryAdapter.kt 구현 * feat: MissionErrorCode.kt, MissionPolicy.kt 구현 * feat: MissionFactory.kt 구현 * feat: StudyLogDailyMissionInfo.kt DTO 추가 * feat: DailyMissionInfo DTO 추가 * feat: CreateMissionRequest.kt, UpdateMissionRequest.kt DTO 추가 * feat: CreateMissionResponse.kt, LoadMissionInfoResponse.kt DTO 추가 * feat: MissionInfo.kt, MissionsInfo.kt DTO 추가 * refactor: record → data class 전환에 따른 DTO 접근자 호출 방식(x() → getX()) 수정 * refactor: java studylog application 패키지 제거 (Kotlin 마이그레이션 완료) * refactor: java studylog infra jpa 패키지 제거 (Kotlin 마이그레이션 완료) * refactor: java studylog domain factory 패키지 제거 (Kotlin 마이그레이션 완료) * refactor: java mission presentation, application 패키지 제거 (Kotlin 마이그레이션 완료) * refactor: java mission infra jpa 패키지 제거 (Kotlin 마이그레이션 완료) * refactor: java mission domain error, policy 패키지 제거 (Kotlin 마이그레이션 완료) * test: StudyLogDailyMissionFixture.kt 추가 * test: StudyLogDailyMissionTestHelper.kt 추가 * test: StudyLogDailyMissionQueryServiceTest.kt, StudyLogDailyMissionCommandServiceTest.kt 단위 테스트 추가 * test: DailyMissionFixture.kt 추가 * test: DailyMissionTestHelper.kt 추가 * test: DailyMissionQueryServiceTest.kt, DailyMissionCommandServiceTest.kt 단위 테스트 추가 * test: MissionFixture.kt, CreateMissionRequestFixture.kt, UpdateMissionRequestFixture.kt 추가 * test: MissionTestHelper 추가 * test: MissionQueryServiceTest.kt, MissionCommandServiceTest.kt 단위 테스트 추가 * test: MissionControllerIntegrationTest 통합 테스트 추가 * test: test java studylog 패키지 제거 (Kotlin 마이그레이션 완료) * test: test java mission 패키지 제거 (Kotlin 마이그레이션 완료) * test: StudyLogControllerIntegrationTest.kt IssuePresignedUrl 통합 테스트 개선 * test: StudyLogControllerIntegrationTest.kt ConfirmImage 통합 테스트 개선 --- .../application/facade/MemberFacade.java | 10 +- .../application/dto/DailyMissionInfo.java | 10 - .../mission/application/dto/MissionInfo.java | 22 - .../mission/application/dto/MissionsInfo.java | 9 - .../application/facade/MissionFacade.java | 136 -- .../service/DailyMissionCommandService.java | 47 - .../service/DailyMissionQueryService.java | 47 - .../service/MissionCommandService.java | 66 - .../service/MissionQueryService.java | 48 - .../domain/error/DailyMissionErrorCode.java | 37 - .../domain/error/MissionErrorCode.java | 38 - .../domain/factory/DailyMissionFactory.java | 14 - .../domain/factory/MissionFactory.java | 13 - .../domain/policy/DailyMissionPolicy.java | 27 - .../mission/domain/policy/MissionPolicy.java | 41 - .../repository/DailyMissionRepository.java | 12 - .../domain/repository/MissionRepository.java | 15 - .../infra/jpa/DailyMissionJpaRepository.java | 11 - .../jpa/DailyMissionRepositoryAdapter.java | 28 - .../infra/jpa/MissionJpaRepository.java | 11 - .../infra/jpa/MissionRepositoryAdapter.java | 34 - .../controller/MissionController.java | 89 -- .../dto/request/CreateMissionRequest.java | 8 - .../dto/request/UpdateMissionRequest.java | 8 - .../dto/response/CreateMissionResponse.java | 10 - .../dto/response/LoadMissionInfoResponse.java | 13 - .../stamp/domain/error/StampErrorCode.java | 2 +- .../stamp/domain/policy/StampPolicy.java | 2 +- .../dto/StudyLogDailyMissionInfo.java | 13 - .../StudyLogDailyMissionCommandService.java | 46 - .../StudyLogDailyMissionQueryService.java | 20 - .../factory/StudyLogDailyMissionFactory.java | 14 - .../StudyLogDailyMissionRepository.java | 8 - .../StudyLogDailyMissionJpaRepository.java | 7 - ...StudyLogDailyMissionRepositoryAdapter.java | 18 - .../application/facade/DailyGoalFacade.java | 10 +- .../response/LoadDailyGoalDetailResponse.java | 2 +- .../application/dto/DailyMissionInfo.kt | 17 + .../mission/application/dto/MissionInfo.kt | 26 + .../mission/application/dto/MissionsInfo.kt | 10 + .../application/facade/MissionFacade.kt | 160 +++ .../service/DailyMissionCommandService.kt | 34 + .../service/DailyMissionQueryService.kt | 48 + .../service/MissionCommandService.kt | 58 + .../service/MissionQueryService.kt | 45 + .../domain/error/DailyMissionErrorCode.kt | 25 + .../mission/domain/error/MissionErrorCode.kt | 27 + .../domain/factory/DailyMissionFactory.kt | 13 + .../mission/domain/factory/MissionFactory.kt | 12 + .../domain/policy/DailyMissionPolicy.kt | 31 + .../mission/domain/policy/MissionPolicy.kt | 43 + .../repository/DailyMissionRepository.kt | 11 + .../domain/repository/MissionRepository.kt | 14 + .../infra/jpa/DailyMissionJpaRepository.kt | 8 + .../jpa/DailyMissionRepositoryAdapter.kt | 16 + .../mission/infra/jpa/MissionJpaRepository.kt | 10 + .../infra/jpa/MissionRepositoryAdapter.kt | 20 + .../controller/MissionController.kt | 93 ++ .../dto/request/CreateMissionRequest.kt | 10 + .../dto/request/UpdateMissionRequest.kt | 10 + .../dto/response/CreateMissionResponse.kt | 14 + .../dto/response/LoadMissionInfoResponse.kt | 18 + .../service/PomodoroCommandService.kt | 2 +- .../service/PomodoroQueryService.kt | 2 +- .../domain/error/PomodoroErrorCode.kt | 2 +- .../pomodoro/domain/policy/PomodoroPolicy.kt | 2 +- .../dto/StudyLogDailyMissionInfo.kt | 18 + .../application/facade/StudyLogFacade.kt | 14 +- .../service/StudyLogCommandService.kt | 2 +- .../StudyLogDailyMissionCommandService.kt | 35 + .../StudyLogDailyMissionQueryService.kt | 13 + .../domain/error/StudyLogErrorCode.kt | 2 +- .../factory/StudyLogDailyMissionFactory.kt | 12 + .../StudyLogDailyMissionRepository.kt | 7 + .../jpa/StudyLogDailyMissionJpaRepository.kt | 6 + .../StudyLogDailyMissionRepositoryAdapter.kt | 13 + .../controller/StudyLogController.kt | 7 +- .../dto/response/CreateStudyLogResponse.kt | 2 +- .../DailyMissionCommandServiceTest.java | 214 --- .../service/DailyMissionQueryServiceTest.java | 228 --- .../service/MissionCommandServiceTest.java | 342 ----- .../service/MissionQueryServiceTest.java | 215 --- .../fixture/CreateMissionRequestFixture.java | 16 - .../mission/fixture/DailyMissionFixture.java | 22 - .../mission/fixture/MissionFixture.java | 21 - .../fixture/UpdateMissionRequestFixture.java | 16 - .../helper/DailyMissionTestHelper.java | 27 - .../mission/helper/MissionTestHelper.java | 33 - .../MissionControllerIntegrationTest.java | 1278 ----------------- .../service/StampCommandServiceTest.java | 2 +- .../service/StampQueryServiceTest.java | 2 +- .../StampControllerIntegrationTest.java | 12 +- ...tudyLogDailyMissionCommandServiceTest.java | 228 --- .../StudyLogDailyMissionQueryServiceTest.java | 61 - .../fixture/StudyLogDailyMissionFixture.java | 23 - .../StudyLogDailyMissionTestHelper.java | 22 - .../DailyGoalControllerIntegrationTest.java | 6 +- .../service/DailyMissionCommandServiceTest.kt | 207 +++ .../service/DailyMissionQueryServiceTest.kt | 218 +++ .../service/MissionCommandServiceTest.kt | 321 +++++ .../service/MissionQueryServiceTest.kt | 214 +++ .../fixture/CreateMissionRequestFixture.kt | 11 + .../mission/fixture/DailyMissionFixture.kt | 19 + .../mission/fixture/MissionFixture.kt | 19 + .../fixture/UpdateMissionRequestFixture.kt | 11 + .../mission/helper/DailyMissionTestHelper.kt | 23 + .../mission/helper/MissionTestHelper.kt | 18 + .../MissionControllerIntegrationTest.kt | 1166 +++++++++++++++ .../service/PomodoroCommandServiceTest.kt | 24 +- .../service/PomodoroQueryServiceTest.kt | 10 +- .../pomodoro/fixture/PomodoroFixture.kt | 8 +- .../pomodoro/helper/PomodoroTestHelper.kt | 14 +- .../service/StudyLogCommandServiceTest.kt | 24 +- .../StudyLogDailyMissionCommandServiceTest.kt | 201 +++ .../StudyLogDailyMissionQueryServiceTest.kt | 80 ++ .../service/StudyLogQueryServiceTest.kt | 20 +- .../fixture/StudyLogDailyMissionFixture.kt | 19 + .../helper/StudyLogDailyMissionTestHelper.kt | 19 + .../StudyLogControllerIntegrationTest.kt | 46 +- 119 files changed, 3546 insertions(+), 3772 deletions(-) delete mode 100644 src/main/java/com/ject/studytrip/mission/application/dto/DailyMissionInfo.java delete mode 100644 src/main/java/com/ject/studytrip/mission/application/dto/MissionInfo.java delete mode 100644 src/main/java/com/ject/studytrip/mission/application/dto/MissionsInfo.java delete mode 100644 src/main/java/com/ject/studytrip/mission/application/facade/MissionFacade.java delete mode 100644 src/main/java/com/ject/studytrip/mission/application/service/DailyMissionCommandService.java delete mode 100644 src/main/java/com/ject/studytrip/mission/application/service/DailyMissionQueryService.java delete mode 100644 src/main/java/com/ject/studytrip/mission/application/service/MissionCommandService.java delete mode 100644 src/main/java/com/ject/studytrip/mission/application/service/MissionQueryService.java delete mode 100644 src/main/java/com/ject/studytrip/mission/domain/error/DailyMissionErrorCode.java delete mode 100644 src/main/java/com/ject/studytrip/mission/domain/error/MissionErrorCode.java delete mode 100644 src/main/java/com/ject/studytrip/mission/domain/factory/DailyMissionFactory.java delete mode 100644 src/main/java/com/ject/studytrip/mission/domain/factory/MissionFactory.java delete mode 100644 src/main/java/com/ject/studytrip/mission/domain/policy/DailyMissionPolicy.java delete mode 100644 src/main/java/com/ject/studytrip/mission/domain/policy/MissionPolicy.java delete mode 100644 src/main/java/com/ject/studytrip/mission/domain/repository/DailyMissionRepository.java delete mode 100644 src/main/java/com/ject/studytrip/mission/domain/repository/MissionRepository.java delete mode 100644 src/main/java/com/ject/studytrip/mission/infra/jpa/DailyMissionJpaRepository.java delete mode 100644 src/main/java/com/ject/studytrip/mission/infra/jpa/DailyMissionRepositoryAdapter.java delete mode 100644 src/main/java/com/ject/studytrip/mission/infra/jpa/MissionJpaRepository.java delete mode 100644 src/main/java/com/ject/studytrip/mission/infra/jpa/MissionRepositoryAdapter.java delete mode 100644 src/main/java/com/ject/studytrip/mission/presentation/controller/MissionController.java delete mode 100644 src/main/java/com/ject/studytrip/mission/presentation/dto/request/CreateMissionRequest.java delete mode 100644 src/main/java/com/ject/studytrip/mission/presentation/dto/request/UpdateMissionRequest.java delete mode 100644 src/main/java/com/ject/studytrip/mission/presentation/dto/response/CreateMissionResponse.java delete mode 100644 src/main/java/com/ject/studytrip/mission/presentation/dto/response/LoadMissionInfoResponse.java delete mode 100644 src/main/java/com/ject/studytrip/studylog/application/dto/StudyLogDailyMissionInfo.java delete mode 100644 src/main/java/com/ject/studytrip/studylog/application/service/StudyLogDailyMissionCommandService.java delete mode 100644 src/main/java/com/ject/studytrip/studylog/application/service/StudyLogDailyMissionQueryService.java delete mode 100644 src/main/java/com/ject/studytrip/studylog/domain/factory/StudyLogDailyMissionFactory.java delete mode 100644 src/main/java/com/ject/studytrip/studylog/domain/repository/StudyLogDailyMissionRepository.java delete mode 100644 src/main/java/com/ject/studytrip/studylog/infra/jpa/StudyLogDailyMissionJpaRepository.java delete mode 100644 src/main/java/com/ject/studytrip/studylog/infra/jpa/StudyLogDailyMissionRepositoryAdapter.java create mode 100644 src/main/kotlin/com/ject/studytrip/mission/application/dto/DailyMissionInfo.kt create mode 100644 src/main/kotlin/com/ject/studytrip/mission/application/dto/MissionInfo.kt create mode 100644 src/main/kotlin/com/ject/studytrip/mission/application/dto/MissionsInfo.kt create mode 100644 src/main/kotlin/com/ject/studytrip/mission/application/facade/MissionFacade.kt create mode 100644 src/main/kotlin/com/ject/studytrip/mission/application/service/DailyMissionCommandService.kt create mode 100644 src/main/kotlin/com/ject/studytrip/mission/application/service/DailyMissionQueryService.kt create mode 100644 src/main/kotlin/com/ject/studytrip/mission/application/service/MissionCommandService.kt create mode 100644 src/main/kotlin/com/ject/studytrip/mission/application/service/MissionQueryService.kt create mode 100644 src/main/kotlin/com/ject/studytrip/mission/domain/error/DailyMissionErrorCode.kt create mode 100644 src/main/kotlin/com/ject/studytrip/mission/domain/error/MissionErrorCode.kt create mode 100644 src/main/kotlin/com/ject/studytrip/mission/domain/factory/DailyMissionFactory.kt create mode 100644 src/main/kotlin/com/ject/studytrip/mission/domain/factory/MissionFactory.kt create mode 100644 src/main/kotlin/com/ject/studytrip/mission/domain/policy/DailyMissionPolicy.kt create mode 100644 src/main/kotlin/com/ject/studytrip/mission/domain/policy/MissionPolicy.kt create mode 100644 src/main/kotlin/com/ject/studytrip/mission/domain/repository/DailyMissionRepository.kt create mode 100644 src/main/kotlin/com/ject/studytrip/mission/domain/repository/MissionRepository.kt create mode 100644 src/main/kotlin/com/ject/studytrip/mission/infra/jpa/DailyMissionJpaRepository.kt create mode 100644 src/main/kotlin/com/ject/studytrip/mission/infra/jpa/DailyMissionRepositoryAdapter.kt create mode 100644 src/main/kotlin/com/ject/studytrip/mission/infra/jpa/MissionJpaRepository.kt create mode 100644 src/main/kotlin/com/ject/studytrip/mission/infra/jpa/MissionRepositoryAdapter.kt create mode 100644 src/main/kotlin/com/ject/studytrip/mission/presentation/controller/MissionController.kt create mode 100644 src/main/kotlin/com/ject/studytrip/mission/presentation/dto/request/CreateMissionRequest.kt create mode 100644 src/main/kotlin/com/ject/studytrip/mission/presentation/dto/request/UpdateMissionRequest.kt create mode 100644 src/main/kotlin/com/ject/studytrip/mission/presentation/dto/response/CreateMissionResponse.kt create mode 100644 src/main/kotlin/com/ject/studytrip/mission/presentation/dto/response/LoadMissionInfoResponse.kt create mode 100644 src/main/kotlin/com/ject/studytrip/studylog/application/dto/StudyLogDailyMissionInfo.kt create mode 100644 src/main/kotlin/com/ject/studytrip/studylog/application/service/StudyLogDailyMissionCommandService.kt create mode 100644 src/main/kotlin/com/ject/studytrip/studylog/application/service/StudyLogDailyMissionQueryService.kt create mode 100644 src/main/kotlin/com/ject/studytrip/studylog/domain/factory/StudyLogDailyMissionFactory.kt create mode 100644 src/main/kotlin/com/ject/studytrip/studylog/domain/repository/StudyLogDailyMissionRepository.kt create mode 100644 src/main/kotlin/com/ject/studytrip/studylog/infra/jpa/StudyLogDailyMissionJpaRepository.kt create mode 100644 src/main/kotlin/com/ject/studytrip/studylog/infra/jpa/StudyLogDailyMissionRepositoryAdapter.kt delete mode 100644 src/test/java/com/ject/studytrip/mission/application/service/DailyMissionCommandServiceTest.java delete mode 100644 src/test/java/com/ject/studytrip/mission/application/service/DailyMissionQueryServiceTest.java delete mode 100644 src/test/java/com/ject/studytrip/mission/application/service/MissionCommandServiceTest.java delete mode 100644 src/test/java/com/ject/studytrip/mission/application/service/MissionQueryServiceTest.java delete mode 100644 src/test/java/com/ject/studytrip/mission/fixture/CreateMissionRequestFixture.java delete mode 100644 src/test/java/com/ject/studytrip/mission/fixture/DailyMissionFixture.java delete mode 100644 src/test/java/com/ject/studytrip/mission/fixture/MissionFixture.java delete mode 100644 src/test/java/com/ject/studytrip/mission/fixture/UpdateMissionRequestFixture.java delete mode 100644 src/test/java/com/ject/studytrip/mission/helper/DailyMissionTestHelper.java delete mode 100644 src/test/java/com/ject/studytrip/mission/helper/MissionTestHelper.java delete mode 100644 src/test/java/com/ject/studytrip/mission/presentation/controller/MissionControllerIntegrationTest.java delete mode 100644 src/test/java/com/ject/studytrip/studylog/application/service/StudyLogDailyMissionCommandServiceTest.java delete mode 100644 src/test/java/com/ject/studytrip/studylog/application/service/StudyLogDailyMissionQueryServiceTest.java delete mode 100644 src/test/java/com/ject/studytrip/studylog/fixture/StudyLogDailyMissionFixture.java delete mode 100644 src/test/java/com/ject/studytrip/studylog/helper/StudyLogDailyMissionTestHelper.java create mode 100644 src/test/kotlin/com/ject/studytrip/mission/application/service/DailyMissionCommandServiceTest.kt create mode 100644 src/test/kotlin/com/ject/studytrip/mission/application/service/DailyMissionQueryServiceTest.kt create mode 100644 src/test/kotlin/com/ject/studytrip/mission/application/service/MissionCommandServiceTest.kt create mode 100644 src/test/kotlin/com/ject/studytrip/mission/application/service/MissionQueryServiceTest.kt create mode 100644 src/test/kotlin/com/ject/studytrip/mission/fixture/CreateMissionRequestFixture.kt create mode 100644 src/test/kotlin/com/ject/studytrip/mission/fixture/DailyMissionFixture.kt create mode 100644 src/test/kotlin/com/ject/studytrip/mission/fixture/MissionFixture.kt create mode 100644 src/test/kotlin/com/ject/studytrip/mission/fixture/UpdateMissionRequestFixture.kt create mode 100644 src/test/kotlin/com/ject/studytrip/mission/helper/DailyMissionTestHelper.kt create mode 100644 src/test/kotlin/com/ject/studytrip/mission/helper/MissionTestHelper.kt create mode 100644 src/test/kotlin/com/ject/studytrip/mission/presentation/controller/MissionControllerIntegrationTest.kt create mode 100644 src/test/kotlin/com/ject/studytrip/studylog/application/service/StudyLogDailyMissionCommandServiceTest.kt create mode 100644 src/test/kotlin/com/ject/studytrip/studylog/application/service/StudyLogDailyMissionQueryServiceTest.kt create mode 100644 src/test/kotlin/com/ject/studytrip/studylog/fixture/StudyLogDailyMissionFixture.kt create mode 100644 src/test/kotlin/com/ject/studytrip/studylog/helper/StudyLogDailyMissionTestHelper.kt diff --git a/src/main/java/com/ject/studytrip/member/application/facade/MemberFacade.java b/src/main/java/com/ject/studytrip/member/application/facade/MemberFacade.java index ff60bf6..03ccec7 100644 --- a/src/main/java/com/ject/studytrip/member/application/facade/MemberFacade.java +++ b/src/main/java/com/ject/studytrip/member/application/facade/MemberFacade.java @@ -159,13 +159,13 @@ private void cascadeHardDeleteByMemberId(Long memberId) { tripReportStudyLogCommandService.hardDeleteTripReportStudyLogsByMember(memberId); tripReportCommandService.hardDeleteTripReportsByMember(memberId); - studyLogDailyMissionCommandService.hardDeleteStudyLogDailyMissionsByMember(memberId); - pomodoroCommandService.hardDeletePomodorosByMember(memberId); - studyLogCommandService.hardDeleteStudyLogsByMember(memberId); - dailyMissionCommandService.hardDeleteDailyMissionsByMember(memberId); + studyLogDailyMissionCommandService.hardDeleteStudyLogDailyMissionsOwnedByMember(memberId); + pomodoroCommandService.hardDeletePomodorosOwnedByMember(memberId); + studyLogCommandService.hardDeleteStudyLogsOwnedByMember(memberId); + dailyMissionCommandService.hardDeleteDailyMissionsOwnedByMember(memberId); dailyGoalCommandService.hardDeleteDailyGoalsByMember(memberId); - missionCommandService.hardDeleteMissionsByMember(memberId); + missionCommandService.hardDeleteMissionsOwnedByMember(memberId); stampCommandService.hardDeleteStampsByMember(memberId); tripCommandService.hardDeleteTripsByMember(memberId); memberCommandService.hardDeleteMemberById(memberId); diff --git a/src/main/java/com/ject/studytrip/mission/application/dto/DailyMissionInfo.java b/src/main/java/com/ject/studytrip/mission/application/dto/DailyMissionInfo.java deleted file mode 100644 index 54c6427..0000000 --- a/src/main/java/com/ject/studytrip/mission/application/dto/DailyMissionInfo.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.ject.studytrip.mission.application.dto; - -import com.ject.studytrip.mission.domain.model.DailyMission; - -public record DailyMissionInfo(Long dailyMissionId, MissionInfo missionInfo) { - public static DailyMissionInfo from(DailyMission dailyMission) { - return new DailyMissionInfo( - dailyMission.getId(), MissionInfo.from(dailyMission.getMission())); - } -} diff --git a/src/main/java/com/ject/studytrip/mission/application/dto/MissionInfo.java b/src/main/java/com/ject/studytrip/mission/application/dto/MissionInfo.java deleted file mode 100644 index b3e24e3..0000000 --- a/src/main/java/com/ject/studytrip/mission/application/dto/MissionInfo.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.ject.studytrip.mission.application.dto; - -import com.ject.studytrip.global.util.DateUtil; -import com.ject.studytrip.mission.domain.model.Mission; - -public record MissionInfo( - Long missionId, - String missionName, - boolean completed, - String createdAt, - String updatedAt, - String deletedAt) { - public static MissionInfo from(Mission mission) { - return new MissionInfo( - mission.getId(), - mission.getName(), - mission.isCompleted(), - DateUtil.formatDateTime(mission.getCreatedAt()), - DateUtil.formatDateTime(mission.getUpdatedAt()), - DateUtil.formatDateTime(mission.getDeletedAt())); - } -} diff --git a/src/main/java/com/ject/studytrip/mission/application/dto/MissionsInfo.java b/src/main/java/com/ject/studytrip/mission/application/dto/MissionsInfo.java deleted file mode 100644 index b6a0797..0000000 --- a/src/main/java/com/ject/studytrip/mission/application/dto/MissionsInfo.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.ject.studytrip.mission.application.dto; - -import java.util.List; - -public record MissionsInfo(List missionInfos) { - public static MissionsInfo of(List missionInfos) { - return new MissionsInfo(missionInfos); - } -} diff --git a/src/main/java/com/ject/studytrip/mission/application/facade/MissionFacade.java b/src/main/java/com/ject/studytrip/mission/application/facade/MissionFacade.java deleted file mode 100644 index 09b9eef..0000000 --- a/src/main/java/com/ject/studytrip/mission/application/facade/MissionFacade.java +++ /dev/null @@ -1,136 +0,0 @@ -package com.ject.studytrip.mission.application.facade; - -import static com.ject.studytrip.global.common.constants.CacheNameConstants.*; - -import com.ject.studytrip.mission.application.dto.MissionInfo; -import com.ject.studytrip.mission.application.dto.MissionsInfo; -import com.ject.studytrip.mission.application.service.MissionCommandService; -import com.ject.studytrip.mission.application.service.MissionQueryService; -import com.ject.studytrip.mission.domain.model.Mission; -import com.ject.studytrip.mission.presentation.dto.request.CreateMissionRequest; -import com.ject.studytrip.mission.presentation.dto.request.UpdateMissionRequest; -import com.ject.studytrip.stamp.application.service.StampCommandService; -import com.ject.studytrip.stamp.application.service.StampQueryService; -import com.ject.studytrip.stamp.domain.model.Stamp; -import com.ject.studytrip.trip.application.service.TripQueryService; -import com.ject.studytrip.trip.domain.model.Trip; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.cache.annotation.CacheEvict; -import org.springframework.cache.annotation.Cacheable; -import org.springframework.cache.annotation.Caching; -import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; - -@Component -@RequiredArgsConstructor -public class MissionFacade { - private final TripQueryService tripQueryService; - private final StampQueryService stampQueryService; - private final MissionQueryService missionQueryService; - - private final StampCommandService stampCommandService; - private final MissionCommandService missionCommandService; - - @Caching( - evict = { - @CacheEvict( - cacheNames = MISSIONS, - key = - "T(com.ject.studytrip.global.common.factory.CacheKeyFactory).missions(#memberId, #tripId, #stampId)"), - @CacheEvict( - cacheNames = STAMP, - key = - "T(com.ject.studytrip.global.common.factory.CacheKeyFactory).stamp(#memberId, #tripId, #stampId)"), - @CacheEvict( - cacheNames = TRIP, - key = - "T(com.ject.studytrip.global.common.factory.CacheKeyFactory).trip(#memberId, #tripId)"), - @CacheEvict(cacheNames = TRIPS, allEntries = true), - @CacheEvict( - cacheNames = STAMPS, - key = - "T(com.ject.studytrip.global.common.factory.CacheKeyFactory).stamps(#memberId, #tripId)") - }) - @Transactional - public MissionInfo createMission( - Long memberId, Long tripId, Long stampId, CreateMissionRequest request) { - Stamp stamp = getValidStampFromTripOwnedByMember(memberId, tripId, stampId); - Mission mission = missionCommandService.createMission(stamp, request); - - stampCommandService.increaseTotalMissions(stamp); - return MissionInfo.from(mission); - } - - @Caching( - evict = { - @CacheEvict( - cacheNames = MISSIONS, - key = - "T(com.ject.studytrip.global.common.factory.CacheKeyFactory).missions(#memberId, #tripId, #stampId)"), - @CacheEvict( - cacheNames = STAMP, - key = - "T(com.ject.studytrip.global.common.factory.CacheKeyFactory).stamp(#memberId, #tripId, #stampId)"), - @CacheEvict( - cacheNames = TRIP, - key = - "T(com.ject.studytrip.global.common.factory.CacheKeyFactory).trip(#memberId, #tripId)"), - @CacheEvict(cacheNames = TRIPS, allEntries = true) - }) - @Transactional - public void updateMissionNameIfPresent( - Long memberId, - Long tripId, - Long stampId, - Long missionId, - UpdateMissionRequest request) { - Stamp stamp = getValidStampFromTripOwnedByMember(memberId, tripId, stampId); - Mission mission = missionQueryService.getValidMission(stamp.getId(), missionId); - - missionCommandService.updateMissionNameIfPresent(mission, request); - } - - @Caching( - evict = { - @CacheEvict( - cacheNames = MISSIONS, - key = - "T(com.ject.studytrip.global.common.factory.CacheKeyFactory).missions(#memberId, #tripId, #stampId)"), - @CacheEvict( - cacheNames = STAMP, - key = - "T(com.ject.studytrip.global.common.factory.CacheKeyFactory).stamp(#memberId, #tripId, #stampId)"), - @CacheEvict( - cacheNames = TRIP, - key = - "T(com.ject.studytrip.global.common.factory.CacheKeyFactory).trip(#memberId, #tripId)"), - @CacheEvict(cacheNames = TRIPS, allEntries = true) - }) - @Transactional - public void deleteMission(Long memberId, Long tripId, Long stampId, Long missionId) { - Stamp stamp = getValidStampFromTripOwnedByMember(memberId, tripId, stampId); - Mission mission = missionQueryService.getValidMission(stamp.getId(), missionId); - - missionCommandService.deleteMission(mission); - stampCommandService.decreaseTotalMissions(stamp); - } - - @Cacheable( - cacheNames = MISSIONS, - key = - "T(com.ject.studytrip.global.common.factory.CacheKeyFactory).missions(#memberId, #tripId, #stampId)") - @Transactional(readOnly = true) - public MissionsInfo getMissionsByStamp(Long memberId, Long tripId, Long stampId) { - Stamp stamp = getValidStampFromTripOwnedByMember(memberId, tripId, stampId); - List missions = missionQueryService.getMissionsByStampId(stamp.getId()); - - return MissionsInfo.of(missions.stream().map(MissionInfo::from).toList()); - } - - private Stamp getValidStampFromTripOwnedByMember(Long memberId, Long tripId, Long stampId) { - Trip trip = tripQueryService.getValidTrip(memberId, tripId); - - return stampQueryService.getValidStamp(trip.getId(), stampId); - } -} diff --git a/src/main/java/com/ject/studytrip/mission/application/service/DailyMissionCommandService.java b/src/main/java/com/ject/studytrip/mission/application/service/DailyMissionCommandService.java deleted file mode 100644 index 58a5cfd..0000000 --- a/src/main/java/com/ject/studytrip/mission/application/service/DailyMissionCommandService.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.ject.studytrip.mission.application.service; - -import com.ject.studytrip.mission.domain.factory.DailyMissionFactory; -import com.ject.studytrip.mission.domain.model.DailyMission; -import com.ject.studytrip.mission.domain.model.Mission; -import com.ject.studytrip.mission.domain.repository.DailyMissionCommandRepository; -import com.ject.studytrip.mission.domain.repository.DailyMissionRepository; -import com.ject.studytrip.trip.domain.model.DailyGoal; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class DailyMissionCommandService { - private final DailyMissionRepository dailyMissionRepository; - private final DailyMissionCommandRepository dailyMissionCommandRepository; - - public List createDailyMissions(DailyGoal dailyGoal, List missions) { - List dailyMissions = - missions.stream() - .map(mission -> DailyMissionFactory.create(mission, dailyGoal)) - .toList(); - - return dailyMissionRepository.saveAll(dailyMissions); - } - - public void deleteDailyMission(DailyMission dailyMission) { - dailyMission.updateDeletedAt(); - } - - public long hardDeleteDailyMissions() { - return dailyMissionCommandRepository.deleteAllByDeletedAtIsNotNull(); - } - - public long hardDeleteDailyMissionsOwnedByDeletedMission() { - return dailyMissionCommandRepository.deleteAllByDeletedMissionOwner(); - } - - public long hardDeleteDailyMissionsOwnedByDeletedDailyGoal() { - return dailyMissionCommandRepository.deleteAllByDeletedDailyGoalOwner(); - } - - public long hardDeleteDailyMissionsByMember(Long memberId) { - return dailyMissionCommandRepository.deleteAllByMemberId(memberId); - } -} diff --git a/src/main/java/com/ject/studytrip/mission/application/service/DailyMissionQueryService.java b/src/main/java/com/ject/studytrip/mission/application/service/DailyMissionQueryService.java deleted file mode 100644 index 4d4dc2d..0000000 --- a/src/main/java/com/ject/studytrip/mission/application/service/DailyMissionQueryService.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.ject.studytrip.mission.application.service; - -import com.ject.studytrip.mission.domain.model.DailyMission; -import com.ject.studytrip.mission.domain.policy.DailyMissionPolicy; -import com.ject.studytrip.mission.domain.repository.DailyMissionQueryRepository; -import com.ject.studytrip.mission.domain.repository.DailyMissionRepository; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class DailyMissionQueryService { - private final DailyMissionRepository dailyMissionRepository; - private final DailyMissionQueryRepository dailyMissionQueryRepository; - - public List getValidDailyMissionsByIds( - Long dailyGoalId, List dailyMissionIds) { - List dailyMissions = dailyMissionRepository.findAllByIdIn(dailyMissionIds); - validateDailyMissions(dailyMissions, dailyMissionIds, dailyGoalId); - - return dailyMissions; - } - - public List getValidDailyMissionsWithMissionAndStampByIds( - Long dailyGoalId, List dailyMissionIds) { - List dailyMissions = - dailyMissionQueryRepository.findAllWithMissionAndStampByIds(dailyMissionIds); - validateDailyMissions(dailyMissions, dailyMissionIds, dailyGoalId); - - return dailyMissions; - } - - public List getDailyMissionsByDailyGoal(Long dailyGoalId) { - return dailyMissionQueryRepository.findAllByDailyGoalIdFetchJoinMission(dailyGoalId); - } - - private void validateDailyMissions( - List dailyMissions, List dailyMissionIds, Long dailyGoalId) { - DailyMissionPolicy.validateExistAll(dailyMissions, dailyMissionIds); - dailyMissions.forEach( - dailyMission -> { - DailyMissionPolicy.validateBelongsToDailyGoal(dailyMission, dailyGoalId); - DailyMissionPolicy.validateNotDeleted(dailyMission); - }); - } -} diff --git a/src/main/java/com/ject/studytrip/mission/application/service/MissionCommandService.java b/src/main/java/com/ject/studytrip/mission/application/service/MissionCommandService.java deleted file mode 100644 index bd40b6e..0000000 --- a/src/main/java/com/ject/studytrip/mission/application/service/MissionCommandService.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.ject.studytrip.mission.application.service; - -import com.ject.studytrip.mission.domain.factory.MissionFactory; -import com.ject.studytrip.mission.domain.model.Mission; -import com.ject.studytrip.mission.domain.policy.MissionPolicy; -import com.ject.studytrip.mission.domain.repository.MissionCommandRepository; -import com.ject.studytrip.mission.domain.repository.MissionRepository; -import com.ject.studytrip.mission.presentation.dto.request.CreateMissionRequest; -import com.ject.studytrip.mission.presentation.dto.request.UpdateMissionRequest; -import com.ject.studytrip.stamp.domain.model.Stamp; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class MissionCommandService { - private final MissionRepository missionRepository; - private final MissionCommandRepository missionCommandRepository; - - public Mission createMission(Stamp stamp, CreateMissionRequest request) { - Mission mission = MissionFactory.create(stamp, request.missionName()); - - return missionRepository.save(mission); - } - - public void updateMissionNameIfPresent(Mission mission, UpdateMissionRequest request) { - mission.updateName(request.missionName()); - } - - public void deleteMission(Mission mission) { - mission.updateDeletedAt(); - } - - public void completeMission(Mission mission) { - MissionPolicy.validateNotDeleted(mission); - MissionPolicy.validateCompleted(mission); - - mission.updateCompleted(); - } - - public void validateMissionsBelongsToStamp(Long stampId, List missions) { - for (Mission mission : missions) { - MissionPolicy.validateMissionBelongsToStamp(stampId, mission); - } - } - - public void validateAllMissionsCompletedByStampId(Long stampId) { - boolean exists = - missionCommandRepository.existsByStampIdAndCompletedIsFalseAndDeletedAtIsNull( - stampId); - MissionPolicy.validateAllCompleted(exists); - } - - public long hardDeleteMissions() { - return missionCommandRepository.deleteAllByDeletedAtIsNotNull(); - } - - public long hardDeleteMissionsOwnedByDeletedStamp() { - return missionCommandRepository.deleteAllByDeletedStampOwner(); - } - - public long hardDeleteMissionsByMember(Long memberId) { - return missionCommandRepository.deleteAllByMemberId(memberId); - } -} diff --git a/src/main/java/com/ject/studytrip/mission/application/service/MissionQueryService.java b/src/main/java/com/ject/studytrip/mission/application/service/MissionQueryService.java deleted file mode 100644 index 82eaaf5..0000000 --- a/src/main/java/com/ject/studytrip/mission/application/service/MissionQueryService.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.ject.studytrip.mission.application.service; - -import com.ject.studytrip.global.exception.CustomException; -import com.ject.studytrip.mission.domain.error.MissionErrorCode; -import com.ject.studytrip.mission.domain.model.Mission; -import com.ject.studytrip.mission.domain.policy.MissionPolicy; -import com.ject.studytrip.mission.domain.repository.MissionQueryRepository; -import com.ject.studytrip.mission.domain.repository.MissionRepository; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class MissionQueryService { - private final MissionRepository missionRepository; - private final MissionQueryRepository missionQueryRepository; - - public Mission getValidMission(Long stampId, Long missionId) { - Mission mission = - missionRepository - .findById(missionId) - .orElseThrow(() -> new CustomException(MissionErrorCode.MISSION_NOT_FOUND)); - - MissionPolicy.validateMissionBelongsToStamp(stampId, mission); - MissionPolicy.validateNotDeleted(mission); - MissionPolicy.validateCompleted(mission); - - return mission; - } - - public List getMissionsByStampId(Long stampId) { - return missionRepository.findAllByStampIdAndDeletedAtIsNullOrderByCreatedAt(stampId); - } - - public List getValidMissionsWithStamp(List missionIds) { - List missions = missionQueryRepository.findAllByIdsInFetchJoinStamp(missionIds); - - MissionPolicy.validateExistAll(missions, missionIds); - missions.forEach( - mission -> { - MissionPolicy.validateNotDeleted(mission); - MissionPolicy.validateCompleted(mission); - }); - - return missions; - } -} diff --git a/src/main/java/com/ject/studytrip/mission/domain/error/DailyMissionErrorCode.java b/src/main/java/com/ject/studytrip/mission/domain/error/DailyMissionErrorCode.java deleted file mode 100644 index ebbba03..0000000 --- a/src/main/java/com/ject/studytrip/mission/domain/error/DailyMissionErrorCode.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.ject.studytrip.mission.domain.error; - -import com.ject.studytrip.global.exception.error.ErrorCode; -import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; - -@RequiredArgsConstructor -public enum DailyMissionErrorCode implements ErrorCode { - // 400 - DAILY_MISSION_ALREADY_DELETED(HttpStatus.BAD_REQUEST, "이미 삭제된 데일리 미션입니다."), - - // 403 - DAILY_MISSION_NOT_BELONG_TO_DAILY_GOAL( - HttpStatus.FORBIDDEN, "해당 데일리 미션은 요청한 데일리 목표에 속하지 않습니다."), - - // 404 - DAILY_MISSION_NOT_FOUND(HttpStatus.NOT_FOUND, "요청한 데일리 미션이 존재하지 않습니다."), - ; - - private final HttpStatus status; - private final String message; - - @Override - public String getName() { - return this.name(); - } - - @Override - public HttpStatus getStatus() { - return this.status; - } - - @Override - public String getMessage() { - return this.message; - } -} diff --git a/src/main/java/com/ject/studytrip/mission/domain/error/MissionErrorCode.java b/src/main/java/com/ject/studytrip/mission/domain/error/MissionErrorCode.java deleted file mode 100644 index 261005a..0000000 --- a/src/main/java/com/ject/studytrip/mission/domain/error/MissionErrorCode.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.ject.studytrip.mission.domain.error; - -import com.ject.studytrip.global.exception.error.ErrorCode; -import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; - -@RequiredArgsConstructor -public enum MissionErrorCode implements ErrorCode { - // 400 - MISSION_ALREADY_DELETED(HttpStatus.BAD_REQUEST, "해당 미션은 이미 삭제되었습니다."), - MISSION_ALREADY_COMPLETED(HttpStatus.BAD_REQUEST, "이미 완료된 미션입니다."), - ALL_MISSIONS_NOT_COMPLETED(HttpStatus.BAD_REQUEST, "모든 미션이 완료되지 않았습니다."), - - // 403 - MISSION_NOT_BELONGS_TO_STAMP(HttpStatus.FORBIDDEN, "해당 미션은 요청한 스탬프에 속하지 않습니다."), - - // 404 - MISSION_NOT_FOUND(HttpStatus.NOT_FOUND, "요청한 미션이 존재하지 않습니다."), - ; - - private final HttpStatus status; - private final String message; - - @Override - public String getName() { - return this.name(); - } - - @Override - public HttpStatus getStatus() { - return this.status; - } - - @Override - public String getMessage() { - return this.message; - } -} diff --git a/src/main/java/com/ject/studytrip/mission/domain/factory/DailyMissionFactory.java b/src/main/java/com/ject/studytrip/mission/domain/factory/DailyMissionFactory.java deleted file mode 100644 index 797bf0f..0000000 --- a/src/main/java/com/ject/studytrip/mission/domain/factory/DailyMissionFactory.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.ject.studytrip.mission.domain.factory; - -import com.ject.studytrip.mission.domain.model.DailyMission; -import com.ject.studytrip.mission.domain.model.Mission; -import com.ject.studytrip.trip.domain.model.DailyGoal; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; - -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public class DailyMissionFactory { - public static DailyMission create(Mission mission, DailyGoal dailyGoal) { - return DailyMission.of(mission, dailyGoal); - } -} diff --git a/src/main/java/com/ject/studytrip/mission/domain/factory/MissionFactory.java b/src/main/java/com/ject/studytrip/mission/domain/factory/MissionFactory.java deleted file mode 100644 index fc6ae64..0000000 --- a/src/main/java/com/ject/studytrip/mission/domain/factory/MissionFactory.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.ject.studytrip.mission.domain.factory; - -import com.ject.studytrip.mission.domain.model.Mission; -import com.ject.studytrip.stamp.domain.model.Stamp; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; - -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public class MissionFactory { - public static Mission create(Stamp stamp, String name) { - return Mission.of(stamp, name); - } -} diff --git a/src/main/java/com/ject/studytrip/mission/domain/policy/DailyMissionPolicy.java b/src/main/java/com/ject/studytrip/mission/domain/policy/DailyMissionPolicy.java deleted file mode 100644 index 67890af..0000000 --- a/src/main/java/com/ject/studytrip/mission/domain/policy/DailyMissionPolicy.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.ject.studytrip.mission.domain.policy; - -import com.ject.studytrip.global.exception.CustomException; -import com.ject.studytrip.mission.domain.error.DailyMissionErrorCode; -import com.ject.studytrip.mission.domain.model.DailyMission; -import java.util.List; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; - -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public class DailyMissionPolicy { - public static void validateExistAll( - List foundDailyMissions, List requestedIds) { - boolean isEquals = foundDailyMissions.size() == requestedIds.size(); - if (!isEquals) throw new CustomException(DailyMissionErrorCode.DAILY_MISSION_NOT_FOUND); - } - - public static void validateBelongsToDailyGoal(DailyMission dailyMission, Long dailyGoalId) { - if (!dailyMission.getDailyGoal().getId().equals(dailyGoalId)) - throw new CustomException(DailyMissionErrorCode.DAILY_MISSION_NOT_BELONG_TO_DAILY_GOAL); - } - - public static void validateNotDeleted(DailyMission dailyMission) { - if (dailyMission.getDeletedAt() != null) - throw new CustomException(DailyMissionErrorCode.DAILY_MISSION_ALREADY_DELETED); - } -} diff --git a/src/main/java/com/ject/studytrip/mission/domain/policy/MissionPolicy.java b/src/main/java/com/ject/studytrip/mission/domain/policy/MissionPolicy.java deleted file mode 100644 index 9cdbcc7..0000000 --- a/src/main/java/com/ject/studytrip/mission/domain/policy/MissionPolicy.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.ject.studytrip.mission.domain.policy; - -import com.ject.studytrip.global.exception.CustomException; -import com.ject.studytrip.mission.domain.error.MissionErrorCode; -import com.ject.studytrip.mission.domain.model.Mission; -import java.util.List; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; - -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public class MissionPolicy { - public static void validateMissionBelongsToStamp(Long stampId, Mission mission) { - if (!mission.getStamp().getId().equals(stampId)) { - throw new CustomException(MissionErrorCode.MISSION_NOT_BELONGS_TO_STAMP); - } - } - - public static void validateNotDeleted(Mission mission) { - if (mission.getDeletedAt() != null) { - throw new CustomException(MissionErrorCode.MISSION_ALREADY_DELETED); - } - } - - public static void validateCompleted(Mission mission) { - if (mission.isCompleted()) - throw new CustomException(MissionErrorCode.MISSION_ALREADY_COMPLETED); - } - - public static void validateExistAll(List foundMissions, List requestedIds) { - boolean isEquals = foundMissions.size() == requestedIds.size(); - if (!isEquals) { - throw new CustomException(MissionErrorCode.MISSION_NOT_FOUND); - } - } - - public static void validateAllCompleted(boolean exists) { - if (exists) { - throw new CustomException(MissionErrorCode.ALL_MISSIONS_NOT_COMPLETED); - } - } -} diff --git a/src/main/java/com/ject/studytrip/mission/domain/repository/DailyMissionRepository.java b/src/main/java/com/ject/studytrip/mission/domain/repository/DailyMissionRepository.java deleted file mode 100644 index 9cf4347..0000000 --- a/src/main/java/com/ject/studytrip/mission/domain/repository/DailyMissionRepository.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.ject.studytrip.mission.domain.repository; - -import com.ject.studytrip.mission.domain.model.DailyMission; -import java.util.List; - -public interface DailyMissionRepository { - DailyMission save(DailyMission dailyMission); - - List saveAll(List dailyMissions); - - List findAllByIdIn(List ids); -} diff --git a/src/main/java/com/ject/studytrip/mission/domain/repository/MissionRepository.java b/src/main/java/com/ject/studytrip/mission/domain/repository/MissionRepository.java deleted file mode 100644 index 72c20a3..0000000 --- a/src/main/java/com/ject/studytrip/mission/domain/repository/MissionRepository.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.ject.studytrip.mission.domain.repository; - -import com.ject.studytrip.mission.domain.model.Mission; -import java.util.List; -import java.util.Optional; - -public interface MissionRepository { - List findAllByIdIn(List ids); - - List findAllByStampIdAndDeletedAtIsNullOrderByCreatedAt(Long stampId); - - Optional findById(Long id); - - Mission save(Mission mission); -} diff --git a/src/main/java/com/ject/studytrip/mission/infra/jpa/DailyMissionJpaRepository.java b/src/main/java/com/ject/studytrip/mission/infra/jpa/DailyMissionJpaRepository.java deleted file mode 100644 index be9653e..0000000 --- a/src/main/java/com/ject/studytrip/mission/infra/jpa/DailyMissionJpaRepository.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.ject.studytrip.mission.infra.jpa; - -import com.ject.studytrip.mission.domain.model.DailyMission; -import java.util.List; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface DailyMissionJpaRepository extends JpaRepository { - List findAllByIdIn(List ids); - - List findAllByDailyGoalIdAndDeletedAtIsNull(Long dailyGoalId); -} diff --git a/src/main/java/com/ject/studytrip/mission/infra/jpa/DailyMissionRepositoryAdapter.java b/src/main/java/com/ject/studytrip/mission/infra/jpa/DailyMissionRepositoryAdapter.java deleted file mode 100644 index 3c9b3d8..0000000 --- a/src/main/java/com/ject/studytrip/mission/infra/jpa/DailyMissionRepositoryAdapter.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.ject.studytrip.mission.infra.jpa; - -import com.ject.studytrip.mission.domain.model.DailyMission; -import com.ject.studytrip.mission.domain.repository.DailyMissionRepository; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Repository; - -@Repository -@RequiredArgsConstructor -public class DailyMissionRepositoryAdapter implements DailyMissionRepository { - private final DailyMissionJpaRepository dailyMissionJpaRepository; - - @Override - public DailyMission save(DailyMission dailyMission) { - return dailyMissionJpaRepository.save(dailyMission); - } - - @Override - public List saveAll(List dailyMissions) { - return dailyMissionJpaRepository.saveAll(dailyMissions); - } - - @Override - public List findAllByIdIn(List ids) { - return dailyMissionJpaRepository.findAllByIdIn(ids); - } -} diff --git a/src/main/java/com/ject/studytrip/mission/infra/jpa/MissionJpaRepository.java b/src/main/java/com/ject/studytrip/mission/infra/jpa/MissionJpaRepository.java deleted file mode 100644 index 038c7ab..0000000 --- a/src/main/java/com/ject/studytrip/mission/infra/jpa/MissionJpaRepository.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.ject.studytrip.mission.infra.jpa; - -import com.ject.studytrip.mission.domain.model.Mission; -import java.util.List; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface MissionJpaRepository extends JpaRepository { - List findAllByIdIn(List ids); - - List findAllByStampIdAndDeletedAtIsNullOrderByCreatedAt(Long stampId); -} diff --git a/src/main/java/com/ject/studytrip/mission/infra/jpa/MissionRepositoryAdapter.java b/src/main/java/com/ject/studytrip/mission/infra/jpa/MissionRepositoryAdapter.java deleted file mode 100644 index 283c563..0000000 --- a/src/main/java/com/ject/studytrip/mission/infra/jpa/MissionRepositoryAdapter.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.ject.studytrip.mission.infra.jpa; - -import com.ject.studytrip.mission.domain.model.Mission; -import com.ject.studytrip.mission.domain.repository.MissionRepository; -import java.util.List; -import java.util.Optional; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Repository; - -@Repository -@RequiredArgsConstructor -public class MissionRepositoryAdapter implements MissionRepository { - private final MissionJpaRepository missionJpaRepository; - - @Override - public List findAllByIdIn(List ids) { - return missionJpaRepository.findAllByIdIn(ids); - } - - @Override - public List findAllByStampIdAndDeletedAtIsNullOrderByCreatedAt(Long stampId) { - return missionJpaRepository.findAllByStampIdAndDeletedAtIsNullOrderByCreatedAt(stampId); - } - - @Override - public Optional findById(Long id) { - return missionJpaRepository.findById(id); - } - - @Override - public Mission save(Mission mission) { - return missionJpaRepository.save(mission); - } -} diff --git a/src/main/java/com/ject/studytrip/mission/presentation/controller/MissionController.java b/src/main/java/com/ject/studytrip/mission/presentation/controller/MissionController.java deleted file mode 100644 index 6f68477..0000000 --- a/src/main/java/com/ject/studytrip/mission/presentation/controller/MissionController.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.ject.studytrip.mission.presentation.controller; - -import com.ject.studytrip.global.common.response.StandardResponse; -import com.ject.studytrip.mission.application.dto.MissionInfo; -import com.ject.studytrip.mission.application.dto.MissionsInfo; -import com.ject.studytrip.mission.application.facade.MissionFacade; -import com.ject.studytrip.mission.presentation.dto.request.CreateMissionRequest; -import com.ject.studytrip.mission.presentation.dto.request.UpdateMissionRequest; -import com.ject.studytrip.mission.presentation.dto.response.CreateMissionResponse; -import com.ject.studytrip.mission.presentation.dto.response.LoadMissionInfoResponse; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotNull; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -@Tag(name = "Mission", description = "미션 API") -@RequestMapping("/api") -@RestController -@RequiredArgsConstructor -@Validated -public class MissionController { - private final MissionFacade missionFacade; - - @Operation(summary = "미션 생성", description = "특정 스탬프에 새로운 미션을 생성합니다.") - @PostMapping("/trips/{tripId}/stamps/{stampId}/missions") - public ResponseEntity createMission( - @AuthenticationPrincipal String memberId, - @PathVariable @NotNull(message = "여행 ID는 필수 요청 파라미터입니다.") Long tripId, - @PathVariable @NotNull(message = "스탬프 ID는 필수 요청 파라미터입니다.") Long stampId, - @RequestBody @Valid CreateMissionRequest request) { - MissionInfo result = - missionFacade.createMission(Long.valueOf(memberId), tripId, stampId, request); - - return ResponseEntity.status(HttpStatus.CREATED) - .body( - StandardResponse.success( - HttpStatus.CREATED.value(), CreateMissionResponse.of(result))); - } - - @Operation(summary = "미션 수정", description = "특정 미션의 이름을 수정합니다.") - @PatchMapping("/trips/{tripId}/stamps/{stampId}/missions/{missionId}") - public ResponseEntity updateMission( - @AuthenticationPrincipal String memberId, - @PathVariable @NotNull(message = "여행 ID는 필수 요청 파라미터입니다.") Long tripId, - @PathVariable @NotNull(message = "스탬프 ID는 필수 요청 파라미터입니다.") Long stampId, - @PathVariable @NotNull(message = "미션 ID는 필수 요청 파라미터입니다.") Long missionId, - @RequestBody @Valid UpdateMissionRequest request) { - missionFacade.updateMissionNameIfPresent( - Long.valueOf(memberId), tripId, stampId, missionId, request); - - return ResponseEntity.status(HttpStatus.OK) - .body(StandardResponse.success(HttpStatus.OK.value(), null)); - } - - @Operation(summary = "미션 삭제", description = "특정 미션을 삭제합니다.") - @DeleteMapping("/trips/{tripId}/stamps/{stampId}/missions/{missionId}") - public ResponseEntity deleteMission( - @AuthenticationPrincipal String memberId, - @PathVariable @NotNull(message = "여행 ID는 필수 요청 파라미터입니다.") Long tripId, - @PathVariable @NotNull(message = "스탬프 ID는 필수 요청 파라미터입니다.") Long stampId, - @PathVariable @NotNull(message = "미션 ID는 필수 요청 파라미터입니다.") Long missionId) { - missionFacade.deleteMission(Long.valueOf(memberId), tripId, stampId, missionId); - - return ResponseEntity.status(HttpStatus.OK) - .body(StandardResponse.success(HttpStatus.OK.value(), null)); - } - - @Operation(summary = "미션 목록 조회", description = "특정 스탬프의 미션 목록을 조회합니다.") - @GetMapping("/trips/{tripId}/stamps/{stampId}/missions") - public ResponseEntity loadMissionsByStamp( - @AuthenticationPrincipal String memberId, - @PathVariable @NotNull(message = "여행 ID는 필수 요청 파라미터입니다.") Long tripId, - @PathVariable @NotNull(message = "스탬프 ID는 필수 요청 파라미터입니다.") Long stampId) { - MissionsInfo results = - missionFacade.getMissionsByStamp(Long.valueOf(memberId), tripId, stampId); - List responses = - results.missionInfos().stream().map(LoadMissionInfoResponse::of).toList(); - - return ResponseEntity.status(HttpStatus.OK) - .body(StandardResponse.success(HttpStatus.OK.value(), responses)); - } -} diff --git a/src/main/java/com/ject/studytrip/mission/presentation/dto/request/CreateMissionRequest.java b/src/main/java/com/ject/studytrip/mission/presentation/dto/request/CreateMissionRequest.java deleted file mode 100644 index cc78053..0000000 --- a/src/main/java/com/ject/studytrip/mission/presentation/dto/request/CreateMissionRequest.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.ject.studytrip.mission.presentation.dto.request; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; - -public record CreateMissionRequest( - @Schema(description = "미션 이름") @NotBlank(message = "미션 이름은 필수 요청 값입니다.") - String missionName) {} diff --git a/src/main/java/com/ject/studytrip/mission/presentation/dto/request/UpdateMissionRequest.java b/src/main/java/com/ject/studytrip/mission/presentation/dto/request/UpdateMissionRequest.java deleted file mode 100644 index ebd9769..0000000 --- a/src/main/java/com/ject/studytrip/mission/presentation/dto/request/UpdateMissionRequest.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.ject.studytrip.mission.presentation.dto.request; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; - -public record UpdateMissionRequest( - @Schema(description = "수정할 미션 이름") @NotBlank(message = "새로운 미션 이름은 필수 요청 값입니다.") - String missionName) {} diff --git a/src/main/java/com/ject/studytrip/mission/presentation/dto/response/CreateMissionResponse.java b/src/main/java/com/ject/studytrip/mission/presentation/dto/response/CreateMissionResponse.java deleted file mode 100644 index 6bae2b5..0000000 --- a/src/main/java/com/ject/studytrip/mission/presentation/dto/response/CreateMissionResponse.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.ject.studytrip.mission.presentation.dto.response; - -import com.ject.studytrip.mission.application.dto.MissionInfo; -import io.swagger.v3.oas.annotations.media.Schema; - -public record CreateMissionResponse(@Schema(description = "미션 ID") Long missionId) { - public static CreateMissionResponse of(MissionInfo info) { - return new CreateMissionResponse(info.missionId()); - } -} diff --git a/src/main/java/com/ject/studytrip/mission/presentation/dto/response/LoadMissionInfoResponse.java b/src/main/java/com/ject/studytrip/mission/presentation/dto/response/LoadMissionInfoResponse.java deleted file mode 100644 index d0035f6..0000000 --- a/src/main/java/com/ject/studytrip/mission/presentation/dto/response/LoadMissionInfoResponse.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.ject.studytrip.mission.presentation.dto.response; - -import com.ject.studytrip.mission.application.dto.MissionInfo; -import io.swagger.v3.oas.annotations.media.Schema; - -public record LoadMissionInfoResponse( - @Schema(description = "미션 ID") Long missionId, - @Schema(description = "미션 이름") String missionName, - @Schema(description = "미션 완료여부") boolean completed) { - public static LoadMissionInfoResponse of(MissionInfo info) { - return new LoadMissionInfoResponse(info.missionId(), info.missionName(), info.completed()); - } -} diff --git a/src/main/java/com/ject/studytrip/stamp/domain/error/StampErrorCode.java b/src/main/java/com/ject/studytrip/stamp/domain/error/StampErrorCode.java index 08847a1..f8d32b0 100644 --- a/src/main/java/com/ject/studytrip/stamp/domain/error/StampErrorCode.java +++ b/src/main/java/com/ject/studytrip/stamp/domain/error/StampErrorCode.java @@ -23,7 +23,7 @@ public enum StampErrorCode implements ErrorCode { HttpStatus.BAD_REQUEST, "스탬프의 종료일은 여행 종료일보다 이후일 수 없습니다."), // 403 - STAMP_NOT_BELONG_TO_TRIP(HttpStatus.FORBIDDEN, "해당 스탬프는 요청한 여행에 속하지 않습니다."), + STAMP_NOT_BELONGS_TO_TRIP(HttpStatus.FORBIDDEN, "해당 스탬프는 요청한 여행에 속하지 않습니다."), // 404 STAMP_NOT_FOUND(HttpStatus.NOT_FOUND, "요청한 스탬프가 존재하지 않습니다."), diff --git a/src/main/java/com/ject/studytrip/stamp/domain/policy/StampPolicy.java b/src/main/java/com/ject/studytrip/stamp/domain/policy/StampPolicy.java index 7742225..8edd110 100644 --- a/src/main/java/com/ject/studytrip/stamp/domain/policy/StampPolicy.java +++ b/src/main/java/com/ject/studytrip/stamp/domain/policy/StampPolicy.java @@ -13,7 +13,7 @@ public class StampPolicy { public static void validateStampBelongsToTrip(Long tripId, Stamp stamp) { if (!stamp.getTrip().getId().equals(tripId)) - throw new CustomException(StampErrorCode.STAMP_NOT_BELONG_TO_TRIP); + throw new CustomException(StampErrorCode.STAMP_NOT_BELONGS_TO_TRIP); } public static void validateNotDeleted(Stamp stamp) { diff --git a/src/main/java/com/ject/studytrip/studylog/application/dto/StudyLogDailyMissionInfo.java b/src/main/java/com/ject/studytrip/studylog/application/dto/StudyLogDailyMissionInfo.java deleted file mode 100644 index 31f2bda..0000000 --- a/src/main/java/com/ject/studytrip/studylog/application/dto/StudyLogDailyMissionInfo.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.ject.studytrip.studylog.application.dto; - -import com.ject.studytrip.mission.application.dto.DailyMissionInfo; -import com.ject.studytrip.studylog.domain.model.StudyLogDailyMission; - -public record StudyLogDailyMissionInfo( - Long studyLogDailyMissionId, DailyMissionInfo dailyMissionInfo) { - public static StudyLogDailyMissionInfo from(StudyLogDailyMission studyLogDailyMission) { - return new StudyLogDailyMissionInfo( - studyLogDailyMission.getId(), - DailyMissionInfo.from(studyLogDailyMission.getDailyMission())); - } -} diff --git a/src/main/java/com/ject/studytrip/studylog/application/service/StudyLogDailyMissionCommandService.java b/src/main/java/com/ject/studytrip/studylog/application/service/StudyLogDailyMissionCommandService.java deleted file mode 100644 index d7b64a0..0000000 --- a/src/main/java/com/ject/studytrip/studylog/application/service/StudyLogDailyMissionCommandService.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.ject.studytrip.studylog.application.service; - -import com.ject.studytrip.mission.domain.model.DailyMission; -import com.ject.studytrip.studylog.domain.factory.StudyLogDailyMissionFactory; -import com.ject.studytrip.studylog.domain.model.StudyLog; -import com.ject.studytrip.studylog.domain.model.StudyLogDailyMission; -import com.ject.studytrip.studylog.domain.repository.StudyLogDailyMissionCommandRepository; -import com.ject.studytrip.studylog.domain.repository.StudyLogDailyMissionRepository; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class StudyLogDailyMissionCommandService { - private final StudyLogDailyMissionRepository studyLogDailyMissionRepository; - private final StudyLogDailyMissionCommandRepository studyLogDailyMissionCommandRepository; - - public List createStudyLogDailyMissions( - StudyLog studyLog, List dailyMissions) { - List studyLogDailyMissions = - dailyMissions.stream() - .map( - dailyMission -> - StudyLogDailyMissionFactory.create(studyLog, dailyMission)) - .toList(); - - return studyLogDailyMissionRepository.saveAll(studyLogDailyMissions); - } - - public long hardDeleteStudyLogDailyMissions() { - return studyLogDailyMissionCommandRepository.deleteAllByDeletedAtIsNotNull(); - } - - public long hardDeleteStudyLogDailyMissionsOwnedByDeletedDailyMission() { - return studyLogDailyMissionCommandRepository.deleteAllByDeletedDailyMissionOwner(); - } - - public long hardDeleteStudyLogDailyMissionsOwnedByDeletedStudyLog() { - return studyLogDailyMissionCommandRepository.deleteAllByDeletedStudyLogOwner(); - } - - public long hardDeleteStudyLogDailyMissionsByMember(Long memberId) { - return studyLogDailyMissionCommandRepository.deleteAllByMemberId(memberId); - } -} diff --git a/src/main/java/com/ject/studytrip/studylog/application/service/StudyLogDailyMissionQueryService.java b/src/main/java/com/ject/studytrip/studylog/application/service/StudyLogDailyMissionQueryService.java deleted file mode 100644 index 324e723..0000000 --- a/src/main/java/com/ject/studytrip/studylog/application/service/StudyLogDailyMissionQueryService.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.ject.studytrip.studylog.application.service; - -import com.ject.studytrip.studylog.domain.model.StudyLogDailyMission; -import com.ject.studytrip.studylog.domain.repository.StudyLogDailyMissionQueryRepository; -import java.util.List; -import java.util.Map; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class StudyLogDailyMissionQueryService { - private final StudyLogDailyMissionQueryRepository studyLogDailyMissionQueryRepository; - - public Map> getGroupedStudyLogDailyMissionsByStudyLogIds( - List studyLogIds) { - return studyLogDailyMissionQueryRepository.findStudyLogDailyMissionsGroupedByStudyLogId( - studyLogIds); - } -} diff --git a/src/main/java/com/ject/studytrip/studylog/domain/factory/StudyLogDailyMissionFactory.java b/src/main/java/com/ject/studytrip/studylog/domain/factory/StudyLogDailyMissionFactory.java deleted file mode 100644 index de8ecce..0000000 --- a/src/main/java/com/ject/studytrip/studylog/domain/factory/StudyLogDailyMissionFactory.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.ject.studytrip.studylog.domain.factory; - -import com.ject.studytrip.mission.domain.model.DailyMission; -import com.ject.studytrip.studylog.domain.model.StudyLog; -import com.ject.studytrip.studylog.domain.model.StudyLogDailyMission; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; - -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public class StudyLogDailyMissionFactory { - public static StudyLogDailyMission create(StudyLog studyLog, DailyMission dailyMission) { - return StudyLogDailyMission.of(studyLog, dailyMission); - } -} diff --git a/src/main/java/com/ject/studytrip/studylog/domain/repository/StudyLogDailyMissionRepository.java b/src/main/java/com/ject/studytrip/studylog/domain/repository/StudyLogDailyMissionRepository.java deleted file mode 100644 index 61ed208..0000000 --- a/src/main/java/com/ject/studytrip/studylog/domain/repository/StudyLogDailyMissionRepository.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.ject.studytrip.studylog.domain.repository; - -import com.ject.studytrip.studylog.domain.model.StudyLogDailyMission; -import java.util.List; - -public interface StudyLogDailyMissionRepository { - List saveAll(List studyLogDailyMissions); -} diff --git a/src/main/java/com/ject/studytrip/studylog/infra/jpa/StudyLogDailyMissionJpaRepository.java b/src/main/java/com/ject/studytrip/studylog/infra/jpa/StudyLogDailyMissionJpaRepository.java deleted file mode 100644 index 94f1fae..0000000 --- a/src/main/java/com/ject/studytrip/studylog/infra/jpa/StudyLogDailyMissionJpaRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.ject.studytrip.studylog.infra.jpa; - -import com.ject.studytrip.studylog.domain.model.StudyLogDailyMission; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface StudyLogDailyMissionJpaRepository - extends JpaRepository {} diff --git a/src/main/java/com/ject/studytrip/studylog/infra/jpa/StudyLogDailyMissionRepositoryAdapter.java b/src/main/java/com/ject/studytrip/studylog/infra/jpa/StudyLogDailyMissionRepositoryAdapter.java deleted file mode 100644 index 0e071ae..0000000 --- a/src/main/java/com/ject/studytrip/studylog/infra/jpa/StudyLogDailyMissionRepositoryAdapter.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.ject.studytrip.studylog.infra.jpa; - -import com.ject.studytrip.studylog.domain.model.StudyLogDailyMission; -import com.ject.studytrip.studylog.domain.repository.StudyLogDailyMissionRepository; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Repository; - -@Repository -@RequiredArgsConstructor -public class StudyLogDailyMissionRepositoryAdapter implements StudyLogDailyMissionRepository { - private final StudyLogDailyMissionJpaRepository studyLogDailyMissionJpaRepository; - - @Override - public List saveAll(List studyLogDailyMissions) { - return studyLogDailyMissionJpaRepository.saveAll(studyLogDailyMissions); - } -} diff --git a/src/main/java/com/ject/studytrip/trip/application/facade/DailyGoalFacade.java b/src/main/java/com/ject/studytrip/trip/application/facade/DailyGoalFacade.java index 92011eb..bbfd71a 100644 --- a/src/main/java/com/ject/studytrip/trip/application/facade/DailyGoalFacade.java +++ b/src/main/java/com/ject/studytrip/trip/application/facade/DailyGoalFacade.java @@ -102,14 +102,14 @@ public void updateDailyGoal( public void deleteDailyGoal(Long memberId, Long tripId, Long dailyGoalId) { Trip trip = getValidTripOwnedByMember(memberId, tripId); DailyGoal dailyGoal = dailyGoalQueryService.getValidDailyGoal(trip.getId(), dailyGoalId); - Pomodoro pomodoro = pomodoroQueryService.getValidPomodoroByDailyGoal(dailyGoal.getId()); + Pomodoro pomodoro = pomodoroQueryService.getValidPomodoroByDailyGoalId(dailyGoal.getId()); // 뽀모도로 삭제 pomodoroCommandService.deletePomodoro(pomodoro); // 데일리 미션 삭제 List dailyMissions = - dailyMissionQueryService.getDailyMissionsByDailyGoal(dailyGoal.getId()); + dailyMissionQueryService.getDailyMissionsByDailyGoalId(dailyGoal.getId()); for (DailyMission dailyMission : dailyMissions) { dailyMissionCommandService.deleteDailyMission(dailyMission); } @@ -126,9 +126,9 @@ public void deleteDailyGoal(Long memberId, Long tripId, Long dailyGoalId) { public DailyGoalDetail getDailyGoal(Long memberId, Long tripId, Long dailyGoalId) { Trip trip = getValidTripOwnedByMember(memberId, tripId); DailyGoal dailyGoal = dailyGoalQueryService.getValidDailyGoal(trip.getId(), dailyGoalId); - Pomodoro pomodoro = pomodoroQueryService.getValidPomodoroByDailyGoal(dailyGoal.getId()); + Pomodoro pomodoro = pomodoroQueryService.getValidPomodoroByDailyGoalId(dailyGoal.getId()); List dailyMissions = - dailyMissionQueryService.getDailyMissionsByDailyGoal(dailyGoal.getId()); + dailyMissionQueryService.getDailyMissionsByDailyGoalId(dailyGoal.getId()); return DailyGoalDetail.from( DailyGoalInfo.from(dailyGoal), @@ -143,7 +143,7 @@ private Trip getValidTripOwnedByMember(Long memberId, Long tripId) { } private List getValidMissionsByTripCategory(Trip trip, List missionIds) { - List missions = missionQueryService.getValidMissionsWithStamp(missionIds); + List missions = missionQueryService.getValidMissionsByIds(missionIds); for (Mission mission : missions) { stampCommandService.validateStampBelongsToTrip(trip.getId(), mission.getStamp()); diff --git a/src/main/java/com/ject/studytrip/trip/presentation/dto/response/LoadDailyGoalDetailResponse.java b/src/main/java/com/ject/studytrip/trip/presentation/dto/response/LoadDailyGoalDetailResponse.java index 8998616..94e187a 100644 --- a/src/main/java/com/ject/studytrip/trip/presentation/dto/response/LoadDailyGoalDetailResponse.java +++ b/src/main/java/com/ject/studytrip/trip/presentation/dto/response/LoadDailyGoalDetailResponse.java @@ -42,7 +42,7 @@ public record DailyGoalMissionResponse( @Schema(name = "미션 이름") String missionName) { public static DailyGoalMissionResponse of(DailyMissionInfo info) { return new DailyGoalMissionResponse( - info.dailyMissionId(), info.missionInfo().missionName()); + info.getDailyMissionId(), info.getMissionInfo().getMissionName()); } } } diff --git a/src/main/kotlin/com/ject/studytrip/mission/application/dto/DailyMissionInfo.kt b/src/main/kotlin/com/ject/studytrip/mission/application/dto/DailyMissionInfo.kt new file mode 100644 index 0000000..32da416 --- /dev/null +++ b/src/main/kotlin/com/ject/studytrip/mission/application/dto/DailyMissionInfo.kt @@ -0,0 +1,17 @@ +package com.ject.studytrip.mission.application.dto + +import com.ject.studytrip.mission.domain.model.DailyMission + +data class DailyMissionInfo( + val dailyMissionId: Long, + val missionInfo: MissionInfo, +) { + companion object { + @JvmStatic + fun from(dailyMission: DailyMission): DailyMissionInfo = + DailyMissionInfo( + dailyMission.getId(), + MissionInfo.from(dailyMission.getMission()), + ) + } +} diff --git a/src/main/kotlin/com/ject/studytrip/mission/application/dto/MissionInfo.kt b/src/main/kotlin/com/ject/studytrip/mission/application/dto/MissionInfo.kt new file mode 100644 index 0000000..cc9581d --- /dev/null +++ b/src/main/kotlin/com/ject/studytrip/mission/application/dto/MissionInfo.kt @@ -0,0 +1,26 @@ +package com.ject.studytrip.mission.application.dto + +import com.ject.studytrip.global.util.DateUtil +import com.ject.studytrip.mission.domain.model.Mission + +data class MissionInfo( + val missionId: Long, + val missionName: String, + val completed: Boolean, + val createdAt: String, + val updatedAt: String, + val deletedAt: String?, +) { + companion object { + @JvmStatic + fun from(mission: Mission): MissionInfo = + MissionInfo( + mission.getId(), + mission.getName(), + mission.isCompleted(), + DateUtil.formatDateTime(mission.getCreatedAt()), + DateUtil.formatDateTime(mission.getUpdatedAt()), + mission.getDeletedAt()?.let { DateUtil.formatDateTime(it) }, + ) + } +} diff --git a/src/main/kotlin/com/ject/studytrip/mission/application/dto/MissionsInfo.kt b/src/main/kotlin/com/ject/studytrip/mission/application/dto/MissionsInfo.kt new file mode 100644 index 0000000..1c07ac3 --- /dev/null +++ b/src/main/kotlin/com/ject/studytrip/mission/application/dto/MissionsInfo.kt @@ -0,0 +1,10 @@ +package com.ject.studytrip.mission.application.dto + +data class MissionsInfo( + val missionInfos: List, +) { + companion object { + @JvmStatic + fun of(missionInfos: List): MissionsInfo = MissionsInfo(missionInfos) + } +} diff --git a/src/main/kotlin/com/ject/studytrip/mission/application/facade/MissionFacade.kt b/src/main/kotlin/com/ject/studytrip/mission/application/facade/MissionFacade.kt new file mode 100644 index 0000000..826f72c --- /dev/null +++ b/src/main/kotlin/com/ject/studytrip/mission/application/facade/MissionFacade.kt @@ -0,0 +1,160 @@ +package com.ject.studytrip.mission.application.facade + +import com.ject.studytrip.global.common.constants.CacheNameConstants.MISSIONS +import com.ject.studytrip.global.common.constants.CacheNameConstants.STAMP +import com.ject.studytrip.global.common.constants.CacheNameConstants.STAMPS +import com.ject.studytrip.global.common.constants.CacheNameConstants.TRIP +import com.ject.studytrip.global.common.constants.CacheNameConstants.TRIPS +import com.ject.studytrip.mission.application.dto.MissionInfo +import com.ject.studytrip.mission.application.dto.MissionsInfo +import com.ject.studytrip.mission.application.service.MissionCommandService +import com.ject.studytrip.mission.application.service.MissionQueryService +import com.ject.studytrip.mission.presentation.dto.request.CreateMissionRequest +import com.ject.studytrip.mission.presentation.dto.request.UpdateMissionRequest +import com.ject.studytrip.stamp.application.service.StampCommandService +import com.ject.studytrip.stamp.application.service.StampQueryService +import com.ject.studytrip.stamp.domain.model.Stamp +import com.ject.studytrip.trip.application.service.TripQueryService +import org.springframework.cache.annotation.CacheEvict +import org.springframework.cache.annotation.Caching +import org.springframework.stereotype.Component +import org.springframework.transaction.annotation.Transactional + +@Component +class MissionFacade( + // Query Service + private val tripQueryService: TripQueryService, + private val stampQueryService: StampQueryService, + private val missionQueryService: MissionQueryService, + // Command Service + private val stampCommandService: StampCommandService, + private val missionCommandService: MissionCommandService, +) { + @Caching( + evict = [ + CacheEvict( + cacheNames = [MISSIONS], + key = "T(com.ject.studytrip.global.common.factory.CacheKeyFactory).missions(#memberId, #tripId, #stampId)", + ), + CacheEvict( + cacheNames = [STAMP], + key = "T(com.ject.studytrip.global.common.factory.CacheKeyFactory).stamp(#memberId, #tripId, #stampId)", + ), + CacheEvict( + cacheNames = [TRIP], + key = "T(com.ject.studytrip.global.common.factory.CacheKeyFactory).trip(#memberId, #tripId)", + ), + CacheEvict(cacheNames = [TRIPS], allEntries = true), + CacheEvict( + cacheNames = [STAMPS], + key = "T(com.ject.studytrip.global.common.factory.CacheKeyFactory).stamps(#memberId, #tripId)", + ), + ], + ) + @Transactional + fun createMission( + memberId: Long, + tripId: Long, + stampId: Long, + request: CreateMissionRequest, + ): MissionInfo { + val stamp = getValidStampForTripOwnedByMember(memberId, tripId, stampId) + val mission = missionCommandService.createMission(stamp, request) + + stampCommandService.increaseTotalMissions(stamp) + + return MissionInfo.from(mission) + } + + @Caching( + evict = [ + CacheEvict( + cacheNames = [MISSIONS], + key = "T(com.ject.studytrip.global.common.factory.CacheKeyFactory).missions(#memberId, #tripId, #stampId)", + ), + CacheEvict( + cacheNames = [STAMP], + key = "T(com.ject.studytrip.global.common.factory.CacheKeyFactory).stamp(#memberId, #tripId, #stampId)", + ), + CacheEvict( + cacheNames = [TRIP], + key = "T(com.ject.studytrip.global.common.factory.CacheKeyFactory).trip(#memberId, #tripId)", + ), + CacheEvict(cacheNames = [TRIPS], allEntries = true), + ], + ) + @Transactional + fun updateMissionNameIfPresent( + memberId: Long, + tripId: Long, + stampId: Long, + missionId: Long, + request: UpdateMissionRequest, + ) { + val stamp = getValidStampForTripOwnedByMember(memberId, tripId, stampId) + val mission = missionQueryService.getValidMission(stamp.id, missionId) + + missionCommandService.updateMissionNameIfPresent(mission, request) + } + + @Caching( + evict = [ + CacheEvict( + cacheNames = [MISSIONS], + key = "T(com.ject.studytrip.global.common.factory.CacheKeyFactory).missions(#memberId, #tripId, #stampId)", + ), + CacheEvict( + cacheNames = [STAMP], + key = "T(com.ject.studytrip.global.common.factory.CacheKeyFactory).stamp(#memberId, #tripId, #stampId)", + ), + CacheEvict( + cacheNames = [TRIP], + key = "T(com.ject.studytrip.global.common.factory.CacheKeyFactory).trip(#memberId, #tripId)", + ), + CacheEvict(cacheNames = [TRIPS], allEntries = true), + ], + ) + @Transactional + fun deleteMission( + memberId: Long, + tripId: Long, + stampId: Long, + missionId: Long, + ) { + val stamp = getValidStampForTripOwnedByMember(memberId, tripId, stampId) + val mission = missionQueryService.getValidMission(stamp.id, missionId) + + missionCommandService.deleteMission(mission) + stampCommandService.decreaseTotalMissions(stamp) + } + + @Caching( + evict = [ + CacheEvict( + cacheNames = [MISSIONS], + key = "T(com.ject.studytrip.global.common.factory.CacheKeyFactory).missions(#memberId, #tripId, #stampId)", + ), + ], + ) + @Transactional(readOnly = true) + fun getMissionsByStamp( + memberId: Long, + tripId: Long, + stampId: Long, + ): MissionsInfo { + val stamp = getValidStampForTripOwnedByMember(memberId, tripId, stampId) + val missions = missionQueryService.getMissionsByStampId(stamp.id) + + return MissionsInfo.of(missions.map { MissionInfo.from(it) }) + } + + private fun getValidStampForTripOwnedByMember( + memberId: Long, + tripId: Long, + stampId: Long, + ): Stamp { + val trip = tripQueryService.getValidTrip(memberId, tripId) + + return stampQueryService.getValidStamp(trip.id, stampId) + } +} diff --git a/src/main/kotlin/com/ject/studytrip/mission/application/service/DailyMissionCommandService.kt b/src/main/kotlin/com/ject/studytrip/mission/application/service/DailyMissionCommandService.kt new file mode 100644 index 0000000..5b4e388 --- /dev/null +++ b/src/main/kotlin/com/ject/studytrip/mission/application/service/DailyMissionCommandService.kt @@ -0,0 +1,34 @@ +package com.ject.studytrip.mission.application.service + +import com.ject.studytrip.mission.domain.factory.DailyMissionFactory +import com.ject.studytrip.mission.domain.model.DailyMission +import com.ject.studytrip.mission.domain.model.Mission +import com.ject.studytrip.mission.domain.repository.DailyMissionCommandRepository +import com.ject.studytrip.mission.domain.repository.DailyMissionRepository +import com.ject.studytrip.trip.domain.model.DailyGoal +import org.springframework.stereotype.Service + +@Service +class DailyMissionCommandService( + private val dailyMissionRepository: DailyMissionRepository, + private val dailyMissionCommandRepository: DailyMissionCommandRepository, +) { + fun createDailyMissions( + dailyGoal: DailyGoal, + missions: List, + ): List { + val dailyMissions = missions.map { DailyMissionFactory.create(it, dailyGoal) } + + return dailyMissionRepository.saveAll(dailyMissions) + } + + fun deleteDailyMission(dailyMission: DailyMission) = dailyMission.updateDeletedAt() + + fun hardDeleteDailyMissions(): Long = dailyMissionCommandRepository.deleteAllByDeletedAtIsNotNull() + + fun hardDeleteDailyMissionsOwnedByDeletedMission(): Long = dailyMissionCommandRepository.deleteAllByDeletedMissionOwner() + + fun hardDeleteDailyMissionsOwnedByDeletedDailyGoal(): Long = dailyMissionCommandRepository.deleteAllByDeletedDailyGoalOwner() + + fun hardDeleteDailyMissionsOwnedByMember(memberId: Long): Long = dailyMissionCommandRepository.deleteAllByMemberId(memberId) +} diff --git a/src/main/kotlin/com/ject/studytrip/mission/application/service/DailyMissionQueryService.kt b/src/main/kotlin/com/ject/studytrip/mission/application/service/DailyMissionQueryService.kt new file mode 100644 index 0000000..ee59f19 --- /dev/null +++ b/src/main/kotlin/com/ject/studytrip/mission/application/service/DailyMissionQueryService.kt @@ -0,0 +1,48 @@ +package com.ject.studytrip.mission.application.service + +import com.ject.studytrip.mission.domain.model.DailyMission +import com.ject.studytrip.mission.domain.policy.DailyMissionPolicy +import com.ject.studytrip.mission.domain.repository.DailyMissionQueryRepository +import com.ject.studytrip.mission.domain.repository.DailyMissionRepository +import org.springframework.stereotype.Service + +@Service +class DailyMissionQueryService( + private val dailyMissionRepository: DailyMissionRepository, + private val dailyMissionQueryRepository: DailyMissionQueryRepository, +) { + fun getValidDailyMissionsByIds( + dailyGoalId: Long, + dailyMissionIds: List, + ): List { + val dailyMissions = dailyMissionRepository.findAllByIdIn(dailyMissionIds) + validateDailyMissions(dailyMissions, dailyMissionIds, dailyGoalId) + + return dailyMissions + } + + fun getValidDailyMissionsWithMissionAndStampByIds( + dailyGoalId: Long, + dailyMissionIds: List, + ): List { + val dailyMissions = dailyMissionQueryRepository.findAllWithMissionAndStampByIds(dailyMissionIds) + validateDailyMissions(dailyMissions, dailyMissionIds, dailyGoalId) + + return dailyMissions + } + + fun getDailyMissionsByDailyGoalId(dailyGoalId: Long): List = + dailyMissionQueryRepository.findAllByDailyGoalIdFetchJoinMission(dailyGoalId) + + private fun validateDailyMissions( + dailyMissions: List, + dailyMissionIds: List, + dailyGoalId: Long, + ) { + DailyMissionPolicy.validateExistAll(dailyMissions, dailyMissionIds) + dailyMissions.forEach { dailyMission -> + DailyMissionPolicy.validateBelongsToDailyGoal(dailyMission, dailyGoalId) + DailyMissionPolicy.validateNotDeleted(dailyMission) + } + } +} diff --git a/src/main/kotlin/com/ject/studytrip/mission/application/service/MissionCommandService.kt b/src/main/kotlin/com/ject/studytrip/mission/application/service/MissionCommandService.kt new file mode 100644 index 0000000..61321e3 --- /dev/null +++ b/src/main/kotlin/com/ject/studytrip/mission/application/service/MissionCommandService.kt @@ -0,0 +1,58 @@ +package com.ject.studytrip.mission.application.service + +import com.ject.studytrip.mission.domain.factory.MissionFactory +import com.ject.studytrip.mission.domain.model.Mission +import com.ject.studytrip.mission.domain.policy.MissionPolicy +import com.ject.studytrip.mission.domain.repository.MissionCommandRepository +import com.ject.studytrip.mission.domain.repository.MissionRepository +import com.ject.studytrip.mission.presentation.dto.request.CreateMissionRequest +import com.ject.studytrip.mission.presentation.dto.request.UpdateMissionRequest +import com.ject.studytrip.stamp.domain.model.Stamp +import org.springframework.stereotype.Service + +@Service +class MissionCommandService( + private val missionRepository: MissionRepository, + private val missionCommandRepository: MissionCommandRepository, +) { + fun createMission( + stamp: Stamp, + request: CreateMissionRequest, + ): Mission { + val mission = MissionFactory.create(stamp, request.missionName) + + return missionRepository.save(mission) + } + + fun updateMissionNameIfPresent( + mission: Mission, + request: UpdateMissionRequest, + ) = mission.updateName(request.missionName) + + fun deleteMission(mission: Mission) = mission.updateDeletedAt() + + fun completeMission(mission: Mission) { + MissionPolicy.validateNotDeleted(mission) + MissionPolicy.validateNotCompleted(mission) + + mission.updateCompleted() + } + + fun validateMissionsBelongsToStamp( + stampId: Long, + missions: List, + ) = missions.forEach { + MissionPolicy.validateMissionBelongsToStamp(stampId, it) + } + + fun validateAllMissionsCompletedByStampId(stampId: Long) { + val exists = missionCommandRepository.existsByStampIdAndCompletedIsFalseAndDeletedAtIsNull(stampId) + MissionPolicy.validateAllCompleted(exists) + } + + fun hardDeleteMissions(): Long = missionCommandRepository.deleteAllByDeletedAtIsNotNull() + + fun hardDeleteMissionsOwnedByDeletedStamp(): Long = missionCommandRepository.deleteAllByDeletedStampOwner() + + fun hardDeleteMissionsOwnedByMember(memberId: Long): Long = missionCommandRepository.deleteAllByMemberId(memberId) +} diff --git a/src/main/kotlin/com/ject/studytrip/mission/application/service/MissionQueryService.kt b/src/main/kotlin/com/ject/studytrip/mission/application/service/MissionQueryService.kt new file mode 100644 index 0000000..f0403db --- /dev/null +++ b/src/main/kotlin/com/ject/studytrip/mission/application/service/MissionQueryService.kt @@ -0,0 +1,45 @@ +package com.ject.studytrip.mission.application.service + +import com.ject.studytrip.global.exception.CustomException +import com.ject.studytrip.mission.domain.error.MissionErrorCode +import com.ject.studytrip.mission.domain.model.Mission +import com.ject.studytrip.mission.domain.policy.MissionPolicy +import com.ject.studytrip.mission.domain.repository.MissionQueryRepository +import com.ject.studytrip.mission.domain.repository.MissionRepository +import org.springframework.stereotype.Service + +@Service +class MissionQueryService( + private val missionRepository: MissionRepository, + private val missionQueryRepository: MissionQueryRepository, +) { + fun getValidMission( + stampId: Long, + missionId: Long, + ): Mission { + val mission = + missionRepository + .findById(missionId) + .orElseThrow { CustomException(MissionErrorCode.MISSION_NOT_FOUND) } + + MissionPolicy.validateMissionBelongsToStamp(stampId, mission) + MissionPolicy.validateNotDeleted(mission) + MissionPolicy.validateNotCompleted(mission) + + return mission + } + + fun getMissionsByStampId(stampId: Long): List = missionRepository.findAllByStampIdAndDeletedAtIsNullOrderByCreatedAt(stampId) + + fun getValidMissionsByIds(missionIds: List): List { + val missions = missionQueryRepository.findAllByIdsInFetchJoinStamp(missionIds) + + MissionPolicy.validateExistAll(missions, missionIds) + missions.forEach { mission -> + MissionPolicy.validateNotDeleted(mission) + MissionPolicy.validateNotCompleted(mission) + } + + return missions + } +} diff --git a/src/main/kotlin/com/ject/studytrip/mission/domain/error/DailyMissionErrorCode.kt b/src/main/kotlin/com/ject/studytrip/mission/domain/error/DailyMissionErrorCode.kt new file mode 100644 index 0000000..40a402a --- /dev/null +++ b/src/main/kotlin/com/ject/studytrip/mission/domain/error/DailyMissionErrorCode.kt @@ -0,0 +1,25 @@ +package com.ject.studytrip.mission.domain.error + +import com.ject.studytrip.global.exception.error.ErrorCode +import org.springframework.http.HttpStatus + +enum class DailyMissionErrorCode( + private val status: HttpStatus, + private val message: String, +) : ErrorCode { + // 400 + DAILY_MISSION_ALREADY_DELETED(HttpStatus.BAD_REQUEST, "이미 삭제된 데일리 미션입니다."), + + // 403 + DAILY_MISSION_NOT_BELONGS_TO_DAILY_GOAL(HttpStatus.FORBIDDEN, "해당 데일리 미션은 요청한 데일리 목표에 속하지 않습니다."), + + // 404 + DAILY_MISSION_NOT_FOUND(HttpStatus.NOT_FOUND, "요청한 데일리 미션을 찾을 수 없습니다."), + ; + + override fun getName(): String = name + + override fun getStatus(): HttpStatus = status + + override fun getMessage(): String = message +} diff --git a/src/main/kotlin/com/ject/studytrip/mission/domain/error/MissionErrorCode.kt b/src/main/kotlin/com/ject/studytrip/mission/domain/error/MissionErrorCode.kt new file mode 100644 index 0000000..c4e739c --- /dev/null +++ b/src/main/kotlin/com/ject/studytrip/mission/domain/error/MissionErrorCode.kt @@ -0,0 +1,27 @@ +package com.ject.studytrip.mission.domain.error + +import com.ject.studytrip.global.exception.error.ErrorCode +import org.springframework.http.HttpStatus + +enum class MissionErrorCode( + private val status: HttpStatus, + private val message: String, +) : ErrorCode { + // 400 + MISSION_ALREADY_DELETED(HttpStatus.BAD_REQUEST, "이미 삭제된 미션입니다."), + MISSION_ALREADY_COMPLETED(HttpStatus.BAD_REQUEST, "이미 완료된 미션입니다."), + ALL_MISSIONS_NOT_COMPLETED(HttpStatus.BAD_REQUEST, "모든 미션이 완료되지 않았습니다."), + + // 403 + MISSION_NOT_BELONGS_TO_STAMP(HttpStatus.FORBIDDEN, "해당 미션은 요청한 스탬프에 속하지 않습니다."), + + // 404 + MISSION_NOT_FOUND(HttpStatus.NOT_FOUND, "요청한 미션을 찾을 수 없습니다."), + ; + + override fun getName(): String = name + + override fun getStatus(): HttpStatus = status + + override fun getMessage(): String = message +} diff --git a/src/main/kotlin/com/ject/studytrip/mission/domain/factory/DailyMissionFactory.kt b/src/main/kotlin/com/ject/studytrip/mission/domain/factory/DailyMissionFactory.kt new file mode 100644 index 0000000..8eb540a --- /dev/null +++ b/src/main/kotlin/com/ject/studytrip/mission/domain/factory/DailyMissionFactory.kt @@ -0,0 +1,13 @@ +package com.ject.studytrip.mission.domain.factory + +import com.ject.studytrip.mission.domain.model.DailyMission +import com.ject.studytrip.mission.domain.model.Mission +import com.ject.studytrip.trip.domain.model.DailyGoal + +object DailyMissionFactory { + @JvmStatic + fun create( + mission: Mission, + dailyGoal: DailyGoal, + ): DailyMission = DailyMission.of(mission, dailyGoal) +} diff --git a/src/main/kotlin/com/ject/studytrip/mission/domain/factory/MissionFactory.kt b/src/main/kotlin/com/ject/studytrip/mission/domain/factory/MissionFactory.kt new file mode 100644 index 0000000..de25df3 --- /dev/null +++ b/src/main/kotlin/com/ject/studytrip/mission/domain/factory/MissionFactory.kt @@ -0,0 +1,12 @@ +package com.ject.studytrip.mission.domain.factory + +import com.ject.studytrip.mission.domain.model.Mission +import com.ject.studytrip.stamp.domain.model.Stamp + +object MissionFactory { + @JvmStatic + fun create( + stamp: Stamp, + name: String, + ): Mission = Mission.of(stamp, name) +} diff --git a/src/main/kotlin/com/ject/studytrip/mission/domain/policy/DailyMissionPolicy.kt b/src/main/kotlin/com/ject/studytrip/mission/domain/policy/DailyMissionPolicy.kt new file mode 100644 index 0000000..6282ae3 --- /dev/null +++ b/src/main/kotlin/com/ject/studytrip/mission/domain/policy/DailyMissionPolicy.kt @@ -0,0 +1,31 @@ +package com.ject.studytrip.mission.domain.policy + +import com.ject.studytrip.global.exception.CustomException +import com.ject.studytrip.mission.domain.error.DailyMissionErrorCode +import com.ject.studytrip.mission.domain.model.DailyMission + +object DailyMissionPolicy { + fun validateNotDeleted(dailyMission: DailyMission) { + if (dailyMission.isDeleted) { + throw CustomException(DailyMissionErrorCode.DAILY_MISSION_ALREADY_DELETED) + } + } + + fun validateBelongsToDailyGoal( + dailyMission: DailyMission, + dailyGoalId: Long, + ) { + if (dailyMission.dailyGoal.id != dailyGoalId) { + throw CustomException(DailyMissionErrorCode.DAILY_MISSION_NOT_BELONGS_TO_DAILY_GOAL) + } + } + + fun validateExistAll( + foundDailyMissions: List, + requestedIds: List, + ) { + if (foundDailyMissions.size != requestedIds.size) { + throw CustomException(DailyMissionErrorCode.DAILY_MISSION_NOT_FOUND) + } + } +} diff --git a/src/main/kotlin/com/ject/studytrip/mission/domain/policy/MissionPolicy.kt b/src/main/kotlin/com/ject/studytrip/mission/domain/policy/MissionPolicy.kt new file mode 100644 index 0000000..a6eaa36 --- /dev/null +++ b/src/main/kotlin/com/ject/studytrip/mission/domain/policy/MissionPolicy.kt @@ -0,0 +1,43 @@ +package com.ject.studytrip.mission.domain.policy + +import com.ject.studytrip.global.exception.CustomException +import com.ject.studytrip.mission.domain.error.MissionErrorCode +import com.ject.studytrip.mission.domain.model.Mission + +object MissionPolicy { + fun validateNotDeleted(mission: Mission) { + if (mission.isDeleted) { + throw CustomException(MissionErrorCode.MISSION_ALREADY_DELETED) + } + } + + fun validateNotCompleted(mission: Mission) { + if (mission.isCompleted) { + throw CustomException(MissionErrorCode.MISSION_ALREADY_COMPLETED) + } + } + + fun validateAllCompleted(exists: Boolean) { + if (exists) { + throw CustomException(MissionErrorCode.ALL_MISSIONS_NOT_COMPLETED) + } + } + + fun validateMissionBelongsToStamp( + stampId: Long, + mission: Mission, + ) { + if (mission.stamp.id != stampId) { + throw CustomException(MissionErrorCode.MISSION_NOT_BELONGS_TO_STAMP) + } + } + + fun validateExistAll( + foundMissions: List, + requestedIds: List, + ) { + if (foundMissions.size != requestedIds.size) { + throw CustomException(MissionErrorCode.MISSION_NOT_FOUND) + } + } +} diff --git a/src/main/kotlin/com/ject/studytrip/mission/domain/repository/DailyMissionRepository.kt b/src/main/kotlin/com/ject/studytrip/mission/domain/repository/DailyMissionRepository.kt new file mode 100644 index 0000000..88c04b0 --- /dev/null +++ b/src/main/kotlin/com/ject/studytrip/mission/domain/repository/DailyMissionRepository.kt @@ -0,0 +1,11 @@ +package com.ject.studytrip.mission.domain.repository + +import com.ject.studytrip.mission.domain.model.DailyMission + +interface DailyMissionRepository { + fun save(dailyMission: DailyMission): DailyMission + + fun saveAll(dailyMissions: List): List + + fun findAllByIdIn(dailyMissionIds: List): List +} diff --git a/src/main/kotlin/com/ject/studytrip/mission/domain/repository/MissionRepository.kt b/src/main/kotlin/com/ject/studytrip/mission/domain/repository/MissionRepository.kt new file mode 100644 index 0000000..e414d55 --- /dev/null +++ b/src/main/kotlin/com/ject/studytrip/mission/domain/repository/MissionRepository.kt @@ -0,0 +1,14 @@ +package com.ject.studytrip.mission.domain.repository + +import com.ject.studytrip.mission.domain.model.Mission +import java.util.Optional + +interface MissionRepository { + fun findAllByIdIn(missionIds: List): List + + fun findAllByStampIdAndDeletedAtIsNullOrderByCreatedAt(stampId: Long): List + + fun findById(missionId: Long): Optional + + fun save(mission: Mission): Mission +} diff --git a/src/main/kotlin/com/ject/studytrip/mission/infra/jpa/DailyMissionJpaRepository.kt b/src/main/kotlin/com/ject/studytrip/mission/infra/jpa/DailyMissionJpaRepository.kt new file mode 100644 index 0000000..c481261 --- /dev/null +++ b/src/main/kotlin/com/ject/studytrip/mission/infra/jpa/DailyMissionJpaRepository.kt @@ -0,0 +1,8 @@ +package com.ject.studytrip.mission.infra.jpa + +import com.ject.studytrip.mission.domain.model.DailyMission +import org.springframework.data.jpa.repository.JpaRepository + +interface DailyMissionJpaRepository : JpaRepository { + fun findAllByIdIn(dailyMissionIds: List): List +} diff --git a/src/main/kotlin/com/ject/studytrip/mission/infra/jpa/DailyMissionRepositoryAdapter.kt b/src/main/kotlin/com/ject/studytrip/mission/infra/jpa/DailyMissionRepositoryAdapter.kt new file mode 100644 index 0000000..0a76429 --- /dev/null +++ b/src/main/kotlin/com/ject/studytrip/mission/infra/jpa/DailyMissionRepositoryAdapter.kt @@ -0,0 +1,16 @@ +package com.ject.studytrip.mission.infra.jpa + +import com.ject.studytrip.mission.domain.model.DailyMission +import com.ject.studytrip.mission.domain.repository.DailyMissionRepository +import org.springframework.stereotype.Repository + +@Repository +class DailyMissionRepositoryAdapter( + private val dailyMissionJpaRepository: DailyMissionJpaRepository, +) : DailyMissionRepository { + override fun save(dailyMission: DailyMission): DailyMission = dailyMissionJpaRepository.save(dailyMission) + + override fun saveAll(dailyMissions: List): List = dailyMissionJpaRepository.saveAll(dailyMissions) + + override fun findAllByIdIn(dailyMissionIds: List): List = dailyMissionJpaRepository.findAllByIdIn(dailyMissionIds) +} diff --git a/src/main/kotlin/com/ject/studytrip/mission/infra/jpa/MissionJpaRepository.kt b/src/main/kotlin/com/ject/studytrip/mission/infra/jpa/MissionJpaRepository.kt new file mode 100644 index 0000000..fff0e79 --- /dev/null +++ b/src/main/kotlin/com/ject/studytrip/mission/infra/jpa/MissionJpaRepository.kt @@ -0,0 +1,10 @@ +package com.ject.studytrip.mission.infra.jpa + +import com.ject.studytrip.mission.domain.model.Mission +import org.springframework.data.jpa.repository.JpaRepository + +interface MissionJpaRepository : JpaRepository { + fun findAllByIdIn(missionIds: List): List + + fun findAllByStampIdAndDeletedAtIsNullOrderByCreatedAt(stampId: Long): List +} diff --git a/src/main/kotlin/com/ject/studytrip/mission/infra/jpa/MissionRepositoryAdapter.kt b/src/main/kotlin/com/ject/studytrip/mission/infra/jpa/MissionRepositoryAdapter.kt new file mode 100644 index 0000000..877169d --- /dev/null +++ b/src/main/kotlin/com/ject/studytrip/mission/infra/jpa/MissionRepositoryAdapter.kt @@ -0,0 +1,20 @@ +package com.ject.studytrip.mission.infra.jpa + +import com.ject.studytrip.mission.domain.model.Mission +import com.ject.studytrip.mission.domain.repository.MissionRepository +import org.springframework.stereotype.Repository +import java.util.Optional + +@Repository +class MissionRepositoryAdapter( + private val missionJpaRepository: MissionJpaRepository, +) : MissionRepository { + override fun findAllByIdIn(missionIds: List): List = missionJpaRepository.findAllByIdIn(missionIds) + + override fun findAllByStampIdAndDeletedAtIsNullOrderByCreatedAt(stampId: Long): List = + missionJpaRepository.findAllByStampIdAndDeletedAtIsNullOrderByCreatedAt(stampId) + + override fun findById(missionId: Long): Optional = missionJpaRepository.findById(missionId) + + override fun save(mission: Mission): Mission = missionJpaRepository.save(mission) +} diff --git a/src/main/kotlin/com/ject/studytrip/mission/presentation/controller/MissionController.kt b/src/main/kotlin/com/ject/studytrip/mission/presentation/controller/MissionController.kt new file mode 100644 index 0000000..7bf22e6 --- /dev/null +++ b/src/main/kotlin/com/ject/studytrip/mission/presentation/controller/MissionController.kt @@ -0,0 +1,93 @@ +package com.ject.studytrip.mission.presentation.controller + +import com.ject.studytrip.global.common.response.StandardResponse +import com.ject.studytrip.mission.application.facade.MissionFacade +import com.ject.studytrip.mission.presentation.dto.request.CreateMissionRequest +import com.ject.studytrip.mission.presentation.dto.request.UpdateMissionRequest +import com.ject.studytrip.mission.presentation.dto.response.CreateMissionResponse +import com.ject.studytrip.mission.presentation.dto.response.LoadMissionInfoResponse +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.tags.Tag +import jakarta.validation.Valid +import jakarta.validation.constraints.NotNull +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.security.core.annotation.AuthenticationPrincipal +import org.springframework.validation.annotation.Validated +import org.springframework.web.bind.annotation.DeleteMapping +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PatchMapping +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 +import org.springframework.web.bind.annotation.RestController + +@Tag(name = "Mission", description = "미션 API") +@RestController +@RequestMapping("/api/trips/{tripId}/stamps/{stampId}/missions") +@Validated +class MissionController( + private val missionFacade: MissionFacade, +) { + @Operation(summary = "미션 생성", description = "특정 스탬프에 새로운 미션을 생성합니다.") + @PostMapping + fun createMission( + @AuthenticationPrincipal memberId: String, + @PathVariable @NotNull(message = "여행 ID는 필수 요청 파라미터입니다.") tripId: Long, + @PathVariable @NotNull(message = "스탬프 ID는 필수 요청 파라미터입니다.") stampId: Long, + @RequestBody @Valid request: CreateMissionRequest, + ): ResponseEntity { + val result = missionFacade.createMission(memberId.toLong(), tripId, stampId, request) + + return ResponseEntity + .status(HttpStatus.CREATED) + .body(StandardResponse.success(HttpStatus.CREATED.value(), CreateMissionResponse.of(result))) + } + + @Operation(summary = "미션 수정", description = "특정 미션의 이름을 수정합니다.") + @PatchMapping("/{missionId}") + fun updateMission( + @AuthenticationPrincipal memberId: String, + @PathVariable @NotNull(message = "여행 ID는 필수 요청 파라미터입니다.") tripId: Long, + @PathVariable @NotNull(message = "스탬프 ID는 필수 요청 파라미터입니다.") stampId: Long, + @PathVariable @NotNull(message = "미션 ID는 필수 요청 파라미터입니다.") missionId: Long, + @RequestBody @Valid request: UpdateMissionRequest, + ): ResponseEntity { + missionFacade.updateMissionNameIfPresent(memberId.toLong(), tripId, stampId, missionId, request) + + return ResponseEntity + .status(HttpStatus.OK) + .body(StandardResponse.success(HttpStatus.OK.value(), null)) + } + + @Operation(summary = "미션 삭제", description = "특정 미션을 삭제합니다.") + @DeleteMapping("/{missionId}") + fun deleteMission( + @AuthenticationPrincipal memberId: String, + @PathVariable @NotNull(message = "여행 ID는 필수 요청 파라미터입니다.") tripId: Long, + @PathVariable @NotNull(message = "스탬프 ID는 필수 요청 파라미터입니다.") stampId: Long, + @PathVariable @NotNull(message = "미션 ID는 필수 요청 파라미터입니다.") missionId: Long, + ): ResponseEntity { + missionFacade.deleteMission(memberId.toLong(), tripId, stampId, missionId) + + return ResponseEntity + .status(HttpStatus.OK) + .body(StandardResponse.success(HttpStatus.OK.value(), null)) + } + + @Operation(summary = "미션 목록 조회", description = "특정 스탬프의 미션 목록을 조회합니다.") + @GetMapping + fun loadMissionsByStamp( + @AuthenticationPrincipal memberId: String, + @PathVariable @NotNull(message = "여행 ID는 필수 요청 파라미터입니다.") tripId: Long, + @PathVariable @NotNull(message = "스탬프 ID는 필수 요청 파라미터입니다.") stampId: Long, + ): ResponseEntity { + val results = missionFacade.getMissionsByStamp(memberId.toLong(), tripId, stampId) + val responses = results.missionInfos.map { LoadMissionInfoResponse.of(it) } + + return ResponseEntity + .status(HttpStatus.OK) + .body(StandardResponse.success(HttpStatus.OK.value(), responses)) + } +} diff --git a/src/main/kotlin/com/ject/studytrip/mission/presentation/dto/request/CreateMissionRequest.kt b/src/main/kotlin/com/ject/studytrip/mission/presentation/dto/request/CreateMissionRequest.kt new file mode 100644 index 0000000..b21a8e8 --- /dev/null +++ b/src/main/kotlin/com/ject/studytrip/mission/presentation/dto/request/CreateMissionRequest.kt @@ -0,0 +1,10 @@ +package com.ject.studytrip.mission.presentation.dto.request + +import io.swagger.v3.oas.annotations.media.Schema +import jakarta.validation.constraints.NotBlank + +data class CreateMissionRequest( + @field:Schema(description = "미션 이름") + @field:NotBlank(message = "미션 이름은 필수 요청 값입니다.") + val missionName: String, +) diff --git a/src/main/kotlin/com/ject/studytrip/mission/presentation/dto/request/UpdateMissionRequest.kt b/src/main/kotlin/com/ject/studytrip/mission/presentation/dto/request/UpdateMissionRequest.kt new file mode 100644 index 0000000..a93e384 --- /dev/null +++ b/src/main/kotlin/com/ject/studytrip/mission/presentation/dto/request/UpdateMissionRequest.kt @@ -0,0 +1,10 @@ +package com.ject.studytrip.mission.presentation.dto.request + +import io.swagger.v3.oas.annotations.media.Schema +import jakarta.validation.constraints.NotBlank + +data class UpdateMissionRequest( + @field:Schema(description = "수정할 미션 이름") + @field:NotBlank(message = "새로운 미션 이름은 필수 요청 값입니다.") + val missionName: String, +) diff --git a/src/main/kotlin/com/ject/studytrip/mission/presentation/dto/response/CreateMissionResponse.kt b/src/main/kotlin/com/ject/studytrip/mission/presentation/dto/response/CreateMissionResponse.kt new file mode 100644 index 0000000..baac955 --- /dev/null +++ b/src/main/kotlin/com/ject/studytrip/mission/presentation/dto/response/CreateMissionResponse.kt @@ -0,0 +1,14 @@ +package com.ject.studytrip.mission.presentation.dto.response + +import com.ject.studytrip.mission.application.dto.MissionInfo +import io.swagger.v3.oas.annotations.media.Schema + +data class CreateMissionResponse( + @field:Schema(description = "미션 ID") + val missionId: Long, +) { + companion object { + @JvmStatic + fun of(info: MissionInfo): CreateMissionResponse = CreateMissionResponse(info.missionId) + } +} diff --git a/src/main/kotlin/com/ject/studytrip/mission/presentation/dto/response/LoadMissionInfoResponse.kt b/src/main/kotlin/com/ject/studytrip/mission/presentation/dto/response/LoadMissionInfoResponse.kt new file mode 100644 index 0000000..4824be1 --- /dev/null +++ b/src/main/kotlin/com/ject/studytrip/mission/presentation/dto/response/LoadMissionInfoResponse.kt @@ -0,0 +1,18 @@ +package com.ject.studytrip.mission.presentation.dto.response + +import com.ject.studytrip.mission.application.dto.MissionInfo +import io.swagger.v3.oas.annotations.media.Schema + +data class LoadMissionInfoResponse( + @field:Schema(description = "미션 ID") + val missionId: Long, + @field:Schema(description = "미션 이름") + val missionName: String, + @field:Schema(description = "미션 완료 여부") + val completed: Boolean, +) { + companion object { + @JvmStatic + fun of(info: MissionInfo): LoadMissionInfoResponse = LoadMissionInfoResponse(info.missionId, info.missionName, info.completed) + } +} diff --git a/src/main/kotlin/com/ject/studytrip/pomodoro/application/service/PomodoroCommandService.kt b/src/main/kotlin/com/ject/studytrip/pomodoro/application/service/PomodoroCommandService.kt index 15f99b0..6c77520 100644 --- a/src/main/kotlin/com/ject/studytrip/pomodoro/application/service/PomodoroCommandService.kt +++ b/src/main/kotlin/com/ject/studytrip/pomodoro/application/service/PomodoroCommandService.kt @@ -51,5 +51,5 @@ class PomodoroCommandService( fun hardDeletePomodorosOwnedByDeletedDailyGoal(): Long = pomodoroCommandRepository.deleteAllByDeletedDailyGoalOwner() - fun hardDeletePomodorosByMember(memberId: Long): Long = pomodoroCommandRepository.deleteAllByMemberId(memberId) + fun hardDeletePomodorosOwnedByMember(memberId: Long): Long = pomodoroCommandRepository.deleteAllByMemberId(memberId) } diff --git a/src/main/kotlin/com/ject/studytrip/pomodoro/application/service/PomodoroQueryService.kt b/src/main/kotlin/com/ject/studytrip/pomodoro/application/service/PomodoroQueryService.kt index 0407320..af43003 100644 --- a/src/main/kotlin/com/ject/studytrip/pomodoro/application/service/PomodoroQueryService.kt +++ b/src/main/kotlin/com/ject/studytrip/pomodoro/application/service/PomodoroQueryService.kt @@ -13,7 +13,7 @@ class PomodoroQueryService( private val pomodoroRepository: PomodoroRepository, private val pomodoroQueryRepository: PomodoroQueryRepository, ) { - fun getValidPomodoroByDailyGoal(dailyGoalId: Long): Pomodoro { + fun getValidPomodoroByDailyGoalId(dailyGoalId: Long): Pomodoro { val pomodoro = pomodoroRepository .findByDailyGoalId(dailyGoalId) diff --git a/src/main/kotlin/com/ject/studytrip/pomodoro/domain/error/PomodoroErrorCode.kt b/src/main/kotlin/com/ject/studytrip/pomodoro/domain/error/PomodoroErrorCode.kt index fc29fae..e7c8108 100644 --- a/src/main/kotlin/com/ject/studytrip/pomodoro/domain/error/PomodoroErrorCode.kt +++ b/src/main/kotlin/com/ject/studytrip/pomodoro/domain/error/PomodoroErrorCode.kt @@ -12,7 +12,7 @@ enum class PomodoroErrorCode( POMODORO_NEGATIVE_FOCUS_TIME(HttpStatus.BAD_REQUEST, "뽀모도로 총 집중시간(분)은 음수일 수 없습니다."), // 404 - POMODORO_NOT_FOUND(HttpStatus.NOT_FOUND, "요청한 뽀모도로 정보를 찾을 수 없습니다."), + POMODORO_NOT_FOUND(HttpStatus.NOT_FOUND, "요청한 뽀모도로를 찾을 수 없습니다."), ; override fun getName(): String = name diff --git a/src/main/kotlin/com/ject/studytrip/pomodoro/domain/policy/PomodoroPolicy.kt b/src/main/kotlin/com/ject/studytrip/pomodoro/domain/policy/PomodoroPolicy.kt index 4e0beef..e685a26 100644 --- a/src/main/kotlin/com/ject/studytrip/pomodoro/domain/policy/PomodoroPolicy.kt +++ b/src/main/kotlin/com/ject/studytrip/pomodoro/domain/policy/PomodoroPolicy.kt @@ -6,7 +6,7 @@ import com.ject.studytrip.pomodoro.domain.model.Pomodoro object PomodoroPolicy { fun validateNotDeleted(pomodoro: Pomodoro) { - if (pomodoro.isDeleted()) { + if (pomodoro.isDeleted) { throw CustomException(PomodoroErrorCode.POMODORO_ALREADY_DELETED) } } diff --git a/src/main/kotlin/com/ject/studytrip/studylog/application/dto/StudyLogDailyMissionInfo.kt b/src/main/kotlin/com/ject/studytrip/studylog/application/dto/StudyLogDailyMissionInfo.kt new file mode 100644 index 0000000..175c950 --- /dev/null +++ b/src/main/kotlin/com/ject/studytrip/studylog/application/dto/StudyLogDailyMissionInfo.kt @@ -0,0 +1,18 @@ +package com.ject.studytrip.studylog.application.dto + +import com.ject.studytrip.mission.application.dto.DailyMissionInfo +import com.ject.studytrip.studylog.domain.model.StudyLogDailyMission + +data class StudyLogDailyMissionInfo( + val studyLogDailyMissionId: Long, + val dailyMissionInfo: DailyMissionInfo, +) { + companion object { + @JvmStatic + fun from(studyLogDailyMission: StudyLogDailyMission): StudyLogDailyMissionInfo = + StudyLogDailyMissionInfo( + studyLogDailyMission.getId(), + DailyMissionInfo.from(studyLogDailyMission.getDailyMission()), + ) + } +} diff --git a/src/main/kotlin/com/ject/studytrip/studylog/application/facade/StudyLogFacade.kt b/src/main/kotlin/com/ject/studytrip/studylog/application/facade/StudyLogFacade.kt index 79ef081..a017f72 100644 --- a/src/main/kotlin/com/ject/studytrip/studylog/application/facade/StudyLogFacade.kt +++ b/src/main/kotlin/com/ject/studytrip/studylog/application/facade/StudyLogFacade.kt @@ -75,13 +75,13 @@ class StudyLogFacade( ): StudyLogInfo { // 1. 유효성 검증 및 엔티티 조회 val trip = tripQueryService.getValidTrip(memberId, tripId) - val dailyGoal = dailyGoalQueryService.getValidDailyGoal(trip.getId(), dailyGoalId) + val dailyGoal = dailyGoalQueryService.getValidDailyGoal(trip.id, dailyGoalId) val selectedDailyMissions = dailyMissionQueryService.getValidDailyMissionsWithMissionAndStampByIds( - dailyGoal.getId(), + dailyGoal.id, request.selectedDailyMissionIds, ) - val pomodoro = pomodoroQueryService.getValidPomodoroByDailyGoal(dailyGoal.getId()) + val pomodoro = pomodoroQueryService.getValidPomodoroByDailyGoalId(dailyGoal.id) // 2. 학습 로그 생성 val studyLog = studyLogCommandService.createStudyLog(trip.member, dailyGoal, request.content) @@ -146,7 +146,7 @@ class StudyLogFacade( ) { // 학습 로그 데일리 미션 저장 studyLogDailyMissionCommandService.createStudyLogDailyMissions(studyLog, selectedDailyMissions) - val missions = selectedDailyMissions.map { it.getMission() } + val missions = selectedDailyMissions.map { it.mission } // 스탬프 ID를 기준으로 Stamp 집계 val stampById = mutableMapOf() @@ -155,10 +155,10 @@ class StudyLogFacade( val completeMissionCountByStampId = mutableMapOf() missions.forEach { mission -> - val stamp = mission.getStamp() - stampById.putIfAbsent(stamp.getId(), stamp) + val stamp = mission.stamp + stampById.putIfAbsent(stamp.id, stamp) missionCommandService.completeMission(mission) // 미션 완료 처리 - completeMissionCountByStampId.merge(stamp.getId(), 1) { a, b -> a + b } // 스탬프별 완료한 미션 개수 누적(없으면 1, 있으면 +1) + completeMissionCountByStampId.merge(stamp.id, 1) { a, b -> a + b } // 스탬프별 완료한 미션 개수 누적(없으면 1, 있으면 +1) } // 스탬프별 완료된 미션 수 증가 diff --git a/src/main/kotlin/com/ject/studytrip/studylog/application/service/StudyLogCommandService.kt b/src/main/kotlin/com/ject/studytrip/studylog/application/service/StudyLogCommandService.kt index 1f988e6..adc7201 100644 --- a/src/main/kotlin/com/ject/studytrip/studylog/application/service/StudyLogCommandService.kt +++ b/src/main/kotlin/com/ject/studytrip/studylog/application/service/StudyLogCommandService.kt @@ -39,5 +39,5 @@ class StudyLogCommandService( fun hardDeleteStudyLogsOwnedByDeletedDailyGoal(): Long = studyLogCommandRepository.deleteAllByDeletedDailyGoalOwner() - fun hardDeleteStudyLogsByMember(memberId: Long): Long = studyLogCommandRepository.deleteByMemberId(memberId) + fun hardDeleteStudyLogsOwnedByMember(memberId: Long): Long = studyLogCommandRepository.deleteByMemberId(memberId) } diff --git a/src/main/kotlin/com/ject/studytrip/studylog/application/service/StudyLogDailyMissionCommandService.kt b/src/main/kotlin/com/ject/studytrip/studylog/application/service/StudyLogDailyMissionCommandService.kt new file mode 100644 index 0000000..82c09de --- /dev/null +++ b/src/main/kotlin/com/ject/studytrip/studylog/application/service/StudyLogDailyMissionCommandService.kt @@ -0,0 +1,35 @@ +package com.ject.studytrip.studylog.application.service + +import com.ject.studytrip.mission.domain.model.DailyMission +import com.ject.studytrip.studylog.domain.factory.StudyLogDailyMissionFactory +import com.ject.studytrip.studylog.domain.model.StudyLog +import com.ject.studytrip.studylog.domain.model.StudyLogDailyMission +import com.ject.studytrip.studylog.domain.repository.StudyLogDailyMissionCommandRepository +import com.ject.studytrip.studylog.domain.repository.StudyLogDailyMissionRepository +import org.springframework.stereotype.Service + +@Service +class StudyLogDailyMissionCommandService( + private val studyLogDailyMissionRepository: StudyLogDailyMissionRepository, + private val studyLogDailyMissionCommandRepository: StudyLogDailyMissionCommandRepository, +) { + fun createStudyLogDailyMissions( + studyLog: StudyLog, + dailyMissions: List, + ): List { + val studyLogDailyMissions = dailyMissions.map { StudyLogDailyMissionFactory.create(studyLog, it) } + + return studyLogDailyMissionRepository.saveAll(studyLogDailyMissions) + } + + fun hardDeleteStudyLogDailyMissions(): Long = studyLogDailyMissionCommandRepository.deleteAllByDeletedAtIsNotNull() + + fun hardDeleteStudyLogDailyMissionsOwnedByDeletedDailyMission(): Long = + studyLogDailyMissionCommandRepository.deleteAllByDeletedDailyMissionOwner() + + fun hardDeleteStudyLogDailyMissionsOwnedByDeletedStudyLog(): Long = + studyLogDailyMissionCommandRepository.deleteAllByDeletedStudyLogOwner() + + fun hardDeleteStudyLogDailyMissionsOwnedByMember(memberId: Long): Long = + studyLogDailyMissionCommandRepository.deleteAllByMemberId(memberId) +} diff --git a/src/main/kotlin/com/ject/studytrip/studylog/application/service/StudyLogDailyMissionQueryService.kt b/src/main/kotlin/com/ject/studytrip/studylog/application/service/StudyLogDailyMissionQueryService.kt new file mode 100644 index 0000000..2ecbfdb --- /dev/null +++ b/src/main/kotlin/com/ject/studytrip/studylog/application/service/StudyLogDailyMissionQueryService.kt @@ -0,0 +1,13 @@ +package com.ject.studytrip.studylog.application.service + +import com.ject.studytrip.studylog.domain.model.StudyLogDailyMission +import com.ject.studytrip.studylog.domain.repository.StudyLogDailyMissionQueryRepository +import org.springframework.stereotype.Service + +@Service +class StudyLogDailyMissionQueryService( + private val studyLogDailyMissionQueryRepository: StudyLogDailyMissionQueryRepository, +) { + fun getGroupedStudyLogDailyMissionsByStudyLogIds(studyLogIds: List): Map> = + studyLogDailyMissionQueryRepository.findStudyLogDailyMissionsGroupedByStudyLogId(studyLogIds) +} diff --git a/src/main/kotlin/com/ject/studytrip/studylog/domain/error/StudyLogErrorCode.kt b/src/main/kotlin/com/ject/studytrip/studylog/domain/error/StudyLogErrorCode.kt index 20ef58d..48714ee 100644 --- a/src/main/kotlin/com/ject/studytrip/studylog/domain/error/StudyLogErrorCode.kt +++ b/src/main/kotlin/com/ject/studytrip/studylog/domain/error/StudyLogErrorCode.kt @@ -11,7 +11,7 @@ enum class StudyLogErrorCode( STUDY_LOG_ALREADY_DELETED(HttpStatus.BAD_REQUEST, "이미 삭제된 학습 로그입니다."), // 404 - STUDY_LOG_NOT_FOUND(HttpStatus.NOT_FOUND, "학습 로그를 찾을 수 없습니다."), + STUDY_LOG_NOT_FOUND(HttpStatus.NOT_FOUND, "요청한 학습 로그를 찾을 수 없습니다."), ; override fun getName(): String = name diff --git a/src/main/kotlin/com/ject/studytrip/studylog/domain/factory/StudyLogDailyMissionFactory.kt b/src/main/kotlin/com/ject/studytrip/studylog/domain/factory/StudyLogDailyMissionFactory.kt new file mode 100644 index 0000000..30ae2a7 --- /dev/null +++ b/src/main/kotlin/com/ject/studytrip/studylog/domain/factory/StudyLogDailyMissionFactory.kt @@ -0,0 +1,12 @@ +package com.ject.studytrip.studylog.domain.factory + +import com.ject.studytrip.mission.domain.model.DailyMission +import com.ject.studytrip.studylog.domain.model.StudyLog +import com.ject.studytrip.studylog.domain.model.StudyLogDailyMission + +object StudyLogDailyMissionFactory { + fun create( + studyLog: StudyLog, + dailyMission: DailyMission, + ): StudyLogDailyMission = StudyLogDailyMission.of(studyLog, dailyMission) +} diff --git a/src/main/kotlin/com/ject/studytrip/studylog/domain/repository/StudyLogDailyMissionRepository.kt b/src/main/kotlin/com/ject/studytrip/studylog/domain/repository/StudyLogDailyMissionRepository.kt new file mode 100644 index 0000000..9e8786d --- /dev/null +++ b/src/main/kotlin/com/ject/studytrip/studylog/domain/repository/StudyLogDailyMissionRepository.kt @@ -0,0 +1,7 @@ +package com.ject.studytrip.studylog.domain.repository + +import com.ject.studytrip.studylog.domain.model.StudyLogDailyMission + +interface StudyLogDailyMissionRepository { + fun saveAll(studyLogDailyMissions: List): List +} diff --git a/src/main/kotlin/com/ject/studytrip/studylog/infra/jpa/StudyLogDailyMissionJpaRepository.kt b/src/main/kotlin/com/ject/studytrip/studylog/infra/jpa/StudyLogDailyMissionJpaRepository.kt new file mode 100644 index 0000000..66a29cc --- /dev/null +++ b/src/main/kotlin/com/ject/studytrip/studylog/infra/jpa/StudyLogDailyMissionJpaRepository.kt @@ -0,0 +1,6 @@ +package com.ject.studytrip.studylog.infra.jpa + +import com.ject.studytrip.studylog.domain.model.StudyLogDailyMission +import org.springframework.data.jpa.repository.JpaRepository + +interface StudyLogDailyMissionJpaRepository : JpaRepository diff --git a/src/main/kotlin/com/ject/studytrip/studylog/infra/jpa/StudyLogDailyMissionRepositoryAdapter.kt b/src/main/kotlin/com/ject/studytrip/studylog/infra/jpa/StudyLogDailyMissionRepositoryAdapter.kt new file mode 100644 index 0000000..e757038 --- /dev/null +++ b/src/main/kotlin/com/ject/studytrip/studylog/infra/jpa/StudyLogDailyMissionRepositoryAdapter.kt @@ -0,0 +1,13 @@ +package com.ject.studytrip.studylog.infra.jpa + +import com.ject.studytrip.studylog.domain.model.StudyLogDailyMission +import com.ject.studytrip.studylog.domain.repository.StudyLogDailyMissionRepository +import org.springframework.stereotype.Repository + +@Repository +class StudyLogDailyMissionRepositoryAdapter( + private val studyLogDailyMissionJpaRepository: StudyLogDailyMissionJpaRepository, +) : StudyLogDailyMissionRepository { + override fun saveAll(studyLogDailyMissions: List): List = + studyLogDailyMissionJpaRepository.saveAll(studyLogDailyMissions) +} diff --git a/src/main/kotlin/com/ject/studytrip/studylog/presentation/controller/StudyLogController.kt b/src/main/kotlin/com/ject/studytrip/studylog/presentation/controller/StudyLogController.kt index 1e5a5ea..bda7925 100644 --- a/src/main/kotlin/com/ject/studytrip/studylog/presentation/controller/StudyLogController.kt +++ b/src/main/kotlin/com/ject/studytrip/studylog/presentation/controller/StudyLogController.kt @@ -32,7 +32,7 @@ import org.springframework.web.bind.annotation.RestController class StudyLogController( private val studyLogFacade: StudyLogFacade, ) { - @Operation(summary = "학습 로그 생성", description = "학습을 완료한 데일리 미션을 선택해 학습 로그를 생성하는 API 입니다.") + @Operation(summary = "학습 로그 생성", description = "학습을 완료한 데일리 미션을 선택해 학습 로그를 생성합니다.") @PostMapping("/api/trips/{tripId}/daily-goals/{dailyGoalId}/study-logs") fun createStudyLog( @AuthenticationPrincipal memberId: String, @@ -47,10 +47,7 @@ class StudyLogController( .body(StandardResponse.success(HttpStatus.CREATED.value(), CreateStudyLogResponse.of(result))) } - @Operation( - summary = "여행의 학습 로그 목록 조회", - description = "특정 여행의 학습 로그 목록을 조회하는 API 입니다. 슬라이스를 적용하고 정렬 옵션 LATEST(최신순)/OLDEST(과거순)을 적용합니다.", - ) + @Operation(summary = "여행의 학습 로그 목록 조회", description = "특정 여행의 학습 로그 목록을 조회합니다. 슬라이스를 적용하고 정렬 옵션 LATEST(최신순)/OLDEST(과거순)을 적용합니다.") @GetMapping("/api/trips/{tripId}/study-logs") fun loadStudyLogsByTrip( @AuthenticationPrincipal memberId: String, diff --git a/src/main/kotlin/com/ject/studytrip/studylog/presentation/dto/response/CreateStudyLogResponse.kt b/src/main/kotlin/com/ject/studytrip/studylog/presentation/dto/response/CreateStudyLogResponse.kt index cc184a8..41defe7 100644 --- a/src/main/kotlin/com/ject/studytrip/studylog/presentation/dto/response/CreateStudyLogResponse.kt +++ b/src/main/kotlin/com/ject/studytrip/studylog/presentation/dto/response/CreateStudyLogResponse.kt @@ -9,6 +9,6 @@ data class CreateStudyLogResponse( ) { companion object { @JvmStatic - fun of(info: StudyLogInfo): CreateStudyLogResponse = CreateStudyLogResponse(info.studyLogId) + fun of(studyLogInfo: StudyLogInfo): CreateStudyLogResponse = CreateStudyLogResponse(studyLogInfo.studyLogId) } } diff --git a/src/test/java/com/ject/studytrip/mission/application/service/DailyMissionCommandServiceTest.java b/src/test/java/com/ject/studytrip/mission/application/service/DailyMissionCommandServiceTest.java deleted file mode 100644 index 07bbf63..0000000 --- a/src/test/java/com/ject/studytrip/mission/application/service/DailyMissionCommandServiceTest.java +++ /dev/null @@ -1,214 +0,0 @@ -package com.ject.studytrip.mission.application.service; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; - -import com.ject.studytrip.BaseUnitTest; -import com.ject.studytrip.member.domain.model.Member; -import com.ject.studytrip.member.fixture.MemberFixture; -import com.ject.studytrip.mission.domain.model.DailyMission; -import com.ject.studytrip.mission.domain.model.Mission; -import com.ject.studytrip.mission.domain.repository.DailyMissionCommandRepository; -import com.ject.studytrip.mission.domain.repository.DailyMissionRepository; -import com.ject.studytrip.mission.fixture.DailyMissionFixture; -import com.ject.studytrip.mission.fixture.MissionFixture; -import com.ject.studytrip.stamp.domain.model.Stamp; -import com.ject.studytrip.stamp.fixture.StampFixture; -import com.ject.studytrip.trip.domain.model.DailyGoal; -import com.ject.studytrip.trip.domain.model.Trip; -import com.ject.studytrip.trip.domain.model.TripCategory; -import com.ject.studytrip.trip.fixture.DailyGoalFixture; -import com.ject.studytrip.trip.fixture.TripFixture; -import java.util.List; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; - -@DisplayName("DailyMissionCommandService 단위 테스트") -class DailyMissionCommandServiceTest extends BaseUnitTest { - @InjectMocks private DailyMissionCommandService dailyMissionCommandService; - @Mock private DailyMissionRepository dailyMissionRepository; - @Mock private DailyMissionCommandRepository dailyMissionCommandRepository; - - private Mission mission; - private DailyGoal dailyGoal; - private DailyMission dailyMission; - - @BeforeEach - void setUp() { - Member member = MemberFixture.createMemberFromKakaoWithId(1L); - Trip courseTrip = TripFixture.createTripWithId(1L, member, TripCategory.COURSE); - Stamp stamp = StampFixture.createStampWithId(1L, courseTrip, 1); - mission = MissionFixture.createMissionWithId(1L, stamp); - dailyGoal = DailyGoalFixture.createDailyGoalWithId(1L, courseTrip); - dailyMission = DailyMissionFixture.createDailyMissionWithId(1L, mission, dailyGoal); - } - - @Nested - @DisplayName("createDailyMissions 메서드는") - class CreateDailyMissions { - - @Test - @DisplayName("미션 리스트로 데일리 미션을 생성하여 저장하고 반환한다") - void shouldCreateDailyMissions() { - // given - List missions = List.of(mission); - given(dailyMissionRepository.saveAll(any())).willReturn(List.of(dailyMission)); - - // when - List result = - dailyMissionCommandService.createDailyMissions(dailyGoal, missions); - - // then - assertThat(result.size()).isEqualTo(1); - assertThat(result.get(0).getDailyGoal().getId()).isEqualTo(dailyGoal.getId()); - } - } - - @Nested - @DisplayName("deleteDailyMission 메서드는") - class DeleteDailyMission { - - @Test - @DisplayName("삭제 시 deletedAt을 현재 시각으로 설정한다") - void shouldDeleteDailyMission() { - // when - dailyMissionCommandService.deleteDailyMission(dailyMission); - - // then - assertThat(dailyMission.getDeletedAt()).isNotNull(); - } - } - - @Nested - @DisplayName("hardDeleteDailyMissions 메서드는") - class HardDeleteDailyMissions { - - @Test - @DisplayName("삭제된 데일리 미션이 없으면 0을 반환한다.") - void shouldReturnZeroWhenDeletedDailyMissionsDoNotExist() { - // given - given(dailyMissionCommandRepository.deleteAllByDeletedAtIsNotNull()).willReturn(0L); - - // when - long result = dailyMissionCommandService.hardDeleteDailyMissions(); - - // then - assertThat(result).isEqualTo(0L); - } - - @Test - @DisplayName("삭제된 데일리 미션이 있으면 해당 개수를 반환한다.") - void shouldReturnCountWhenDeletedDailyMissionsExist() { - // given - given(dailyMissionCommandRepository.deleteAllByDeletedAtIsNotNull()).willReturn(5L); - - // when - long result = dailyMissionCommandService.hardDeleteDailyMissions(); - - // then - assertThat(result).isEqualTo(5L); - } - } - - @Nested - @DisplayName("hardDeleteDailyMissionsOwnedByDeletedMission 메서드는") - class HardDeleteDailyMissionsOwnedByDeletedMission { - - @Test - @DisplayName("삭제된 미션이 소유한 데일리 미션이 없으면 0을 반환한다.") - void shouldReturnZeroWhenDailyMissionsOwnedByDeletedMissionDoNotExist() { - // given - given(dailyMissionCommandRepository.deleteAllByDeletedMissionOwner()).willReturn(0L); - - // when - long result = dailyMissionCommandService.hardDeleteDailyMissionsOwnedByDeletedMission(); - - // then - assertThat(result).isEqualTo(0L); - } - - @Test - @DisplayName("삭제된 미션이 소유한 데일리 미션이 있으면 해당 개수를 반환한다.") - void shouldReturnCountWhenDailyMissionsOwnedByDeletedMissionExist() { - // given - given(dailyMissionCommandRepository.deleteAllByDeletedMissionOwner()).willReturn(5L); - - // when - long result = dailyMissionCommandService.hardDeleteDailyMissionsOwnedByDeletedMission(); - - // then - assertThat(result).isEqualTo(5L); - } - } - - @Nested - @DisplayName("hardDeleteDailyMissionsOwnedByDeletedDailyGoal 메서드는") - class HardDeleteDailyMissionsOwnedByDeletedDailyGoal { - - @Test - @DisplayName("삭제된 데일리 목표가 소유한 데일리 미션이 없으면 0을 반환한다.") - void shouldReturnZeroWhenDailyMissionsOwnedByDeletedDailyGoalDoNotExist() { - // given - given(dailyMissionCommandRepository.deleteAllByDeletedDailyGoalOwner()).willReturn(0L); - - // when - long result = - dailyMissionCommandService.hardDeleteDailyMissionsOwnedByDeletedDailyGoal(); - - // then - assertThat(result).isEqualTo(0L); - } - - @Test - @DisplayName("삭제된 데일리 목표가 소유한 데일리 미션이 있으면 해당 개수를 반환한다.") - void shouldReturnCountWhenDailyMissionsOwnedByDeletedDailyGoalExist() { - // given - given(dailyMissionCommandRepository.deleteAllByDeletedDailyGoalOwner()).willReturn(5L); - - // when - long result = - dailyMissionCommandService.hardDeleteDailyMissionsOwnedByDeletedDailyGoal(); - - // then - assertThat(result).isEqualTo(5L); - } - } - - @Nested - @DisplayName("hardDeleteDailyMissionsByMember 메서드는") - class HardDeleteDailyMissionsByMember { - - @Test - @DisplayName("특정 멤버가 소유한 데일리 미션이 없으면 0을 반환한다.") - void shouldReturnZeroWhenDailyMissionsOwnedByMemberDoNotExist() { - // given - Long memberId = 1L; - given(dailyMissionCommandRepository.deleteAllByMemberId(memberId)).willReturn(0L); - - // when - long result = dailyMissionCommandService.hardDeleteDailyMissionsByMember(memberId); - - // then - assertThat(result).isEqualTo(0L); - } - - @Test - @DisplayName("특정 멤버가 소유한 데일리 미션이 있으면 해당 개수를 반환한다.") - void shouldReturnCountWhenDailyMissionsOwnedByMemberExist() { - // given - Long memberId = 1L; - given(dailyMissionCommandRepository.deleteAllByMemberId(memberId)).willReturn(5L); - - // when - long result = dailyMissionCommandService.hardDeleteDailyMissionsByMember(memberId); - - // then - assertThat(result).isEqualTo(5L); - } - } -} diff --git a/src/test/java/com/ject/studytrip/mission/application/service/DailyMissionQueryServiceTest.java b/src/test/java/com/ject/studytrip/mission/application/service/DailyMissionQueryServiceTest.java deleted file mode 100644 index af7b439..0000000 --- a/src/test/java/com/ject/studytrip/mission/application/service/DailyMissionQueryServiceTest.java +++ /dev/null @@ -1,228 +0,0 @@ -package com.ject.studytrip.mission.application.service; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; -import static org.mockito.BDDMockito.given; - -import com.ject.studytrip.BaseUnitTest; -import com.ject.studytrip.global.exception.CustomException; -import com.ject.studytrip.member.domain.model.Member; -import com.ject.studytrip.member.fixture.MemberFixture; -import com.ject.studytrip.mission.domain.error.DailyMissionErrorCode; -import com.ject.studytrip.mission.domain.model.DailyMission; -import com.ject.studytrip.mission.domain.model.Mission; -import com.ject.studytrip.mission.domain.repository.DailyMissionQueryRepository; -import com.ject.studytrip.mission.domain.repository.DailyMissionRepository; -import com.ject.studytrip.mission.fixture.DailyMissionFixture; -import com.ject.studytrip.mission.fixture.MissionFixture; -import com.ject.studytrip.stamp.domain.model.Stamp; -import com.ject.studytrip.stamp.fixture.StampFixture; -import com.ject.studytrip.trip.domain.model.DailyGoal; -import com.ject.studytrip.trip.domain.model.Trip; -import com.ject.studytrip.trip.domain.model.TripCategory; -import com.ject.studytrip.trip.fixture.DailyGoalFixture; -import com.ject.studytrip.trip.fixture.TripFixture; -import java.util.List; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; - -@DisplayName("DailyMissionQueryService 단위 테스트") -class DailyMissionQueryServiceTest extends BaseUnitTest { - @InjectMocks private DailyMissionQueryService dailyMissionQueryService; - @Mock private DailyMissionRepository dailyMissionRepository; - @Mock private DailyMissionQueryRepository dailyMissionQueryRepository; - - private DailyGoal dailyGoal; - private DailyMission dailyMission; - - @BeforeEach - void setUp() { - Member member = MemberFixture.createMemberFromKakaoWithId(1L); - Trip courseTrip = TripFixture.createTripWithId(1L, member, TripCategory.COURSE); - Stamp stamp = StampFixture.createStampWithId(1L, courseTrip, 1); - Mission mission = MissionFixture.createMissionWithId(1L, stamp); - dailyGoal = DailyGoalFixture.createDailyGoalWithId(1L, courseTrip); - dailyMission = DailyMissionFixture.createDailyMissionWithId(1L, mission, dailyGoal); - } - - @Nested - @DisplayName("getValidDailyMissionsByIds 메서드는") - class GetValidDailyMissionsByIds { - - @Test - @DisplayName("요청한 ID 개수와 조회된 데일리 미션 개수가 다르면 예외가 발생한다") - void shouldThrowExceptionWhenSomeDailyMissionsDoNotExist() { - // given - List ids = List.of(1L, 2L); - given(dailyMissionRepository.findAllByIdIn(ids)).willReturn(List.of(dailyMission)); - - // when & then - assertThatThrownBy( - () -> - dailyMissionQueryService.getValidDailyMissionsByIds( - dailyGoal.getId(), ids)) - .isInstanceOf(CustomException.class) - .hasMessage(DailyMissionErrorCode.DAILY_MISSION_NOT_FOUND.getMessage()); - } - - @Test - @DisplayName("데일리 미션이 요청한 데일리 목표에 속하지 않으면 예외가 발생한다") - void shouldThrowExceptionWhenDailyMissionDoesNotBelongToDailyGoal() { - // given - DailyGoal otherGoal = DailyGoalFixture.createDailyGoalWithId(999L, dailyGoal.getTrip()); - List ids = List.of(dailyMission.getId()); - given(dailyMissionRepository.findAllByIdIn(ids)).willReturn(List.of(dailyMission)); - - // when & then - assertThatThrownBy( - () -> - dailyMissionQueryService.getValidDailyMissionsByIds( - otherGoal.getId(), ids)) - .isInstanceOf(CustomException.class) - .hasMessage( - DailyMissionErrorCode.DAILY_MISSION_NOT_BELONG_TO_DAILY_GOAL - .getMessage()); - } - - @Test - @DisplayName("데일리 미션이 이미 삭제된 경우 예외가 발생한다") - void shouldThrowExceptionWhenDailyMissionIsDeleted() { - // given - dailyMission.updateDeletedAt(); // deleted - List ids = List.of(dailyMission.getId()); - given(dailyMissionRepository.findAllByIdIn(ids)).willReturn(List.of(dailyMission)); - - // when & then - Assertions.assertThatThrownBy( - () -> - dailyMissionQueryService.getValidDailyMissionsByIds( - dailyGoal.getId(), ids)) - .isInstanceOf(CustomException.class) - .hasMessage(DailyMissionErrorCode.DAILY_MISSION_ALREADY_DELETED.getMessage()); - } - - @Test - @DisplayName("ID 리스트로 유효한 데일리 미션을 조회해 반환한다") - void shouldGetDailyMissionsByIds() { - // given - List ids = List.of(dailyMission.getId()); - List dailyMissions = List.of(dailyMission); - given(dailyMissionRepository.findAllByIdIn(ids)).willReturn(dailyMissions); - - // when - List result = - dailyMissionQueryService.getValidDailyMissionsByIds(dailyGoal.getId(), ids); - - // then - assertThat(result.isEmpty()).isFalse(); - } - } - - @Nested - @DisplayName("getValidDailyMissionsWithMissionAndStampByIds 메서드는") - class GetValidDailyMissionsWithMissionAndStampByIds { - - @Test - @DisplayName("요청한 ID 개수와 조회된 데일리 미션 개수가 다르면 예외가 발생한다") - void shouldThrowExceptionWhenSomeDailyMissionsDoNotExist() { - // given - List ids = List.of(1L, 2L); - given(dailyMissionQueryRepository.findAllWithMissionAndStampByIds(ids)) - .willReturn(List.of(dailyMission)); - - // when & then - assertThatThrownBy( - () -> - dailyMissionQueryService - .getValidDailyMissionsWithMissionAndStampByIds( - dailyGoal.getId(), ids)) - .isInstanceOf(CustomException.class) - .hasMessage(DailyMissionErrorCode.DAILY_MISSION_NOT_FOUND.getMessage()); - } - - @Test - @DisplayName("데일리 미션이 요청한 데일리 목표에 속하지 않으면 예외가 발생한다") - void shouldThrowExceptionWhenDailyMissionDoesNotBelongToDailyGoal() { - // given - DailyGoal otherGoal = DailyGoalFixture.createDailyGoalWithId(999L, dailyGoal.getTrip()); - List ids = List.of(dailyMission.getId()); - given(dailyMissionQueryRepository.findAllWithMissionAndStampByIds(ids)) - .willReturn(List.of(dailyMission)); - - // when & then - assertThatThrownBy( - () -> - dailyMissionQueryService - .getValidDailyMissionsWithMissionAndStampByIds( - otherGoal.getId(), ids)) - .isInstanceOf(CustomException.class) - .hasMessage( - DailyMissionErrorCode.DAILY_MISSION_NOT_BELONG_TO_DAILY_GOAL - .getMessage()); - } - - @Test - @DisplayName("데일리 미션이 이미 삭제된 경우 예외가 발생한다") - void shouldThrowExceptionWhenDailyMissionIsDeleted() { - // given - dailyMission.updateDeletedAt(); // deleted - List ids = List.of(dailyMission.getId()); - given(dailyMissionQueryRepository.findAllWithMissionAndStampByIds(ids)) - .willReturn(List.of(dailyMission)); - - // when & then - Assertions.assertThatThrownBy( - () -> - dailyMissionQueryService - .getValidDailyMissionsWithMissionAndStampByIds( - dailyGoal.getId(), ids)) - .isInstanceOf(CustomException.class) - .hasMessage(DailyMissionErrorCode.DAILY_MISSION_ALREADY_DELETED.getMessage()); - } - - @Test - @DisplayName("ID 리스트로 유효한 데일리 미션을 미션과 스탬프와 함께 조회해 반환한다") - void shouldGetDailyMissionsWithMissionAndStampByIds() { - // given - List ids = List.of(dailyMission.getId()); - List dailyMissions = List.of(dailyMission); - given(dailyMissionQueryRepository.findAllWithMissionAndStampByIds(ids)) - .willReturn(dailyMissions); - - // when - List result = - dailyMissionQueryService.getValidDailyMissionsWithMissionAndStampByIds( - dailyGoal.getId(), ids); - - // then - assertThat(result.isEmpty()).isFalse(); - } - } - - @Nested - @DisplayName("getDailyMissionsByDailyGoal 메서드는") - class GetDailyMissionsByDailyGoal { - - @Test - @DisplayName("데일리 목표 ID로 데일리 미션 목록을 반환한다") - void shouldGetDailyMissionsByDailyGoalId() { - // given - Long dailyGoalId = dailyGoal.getId(); - List missions = List.of(dailyMission); - given(dailyMissionQueryRepository.findAllByDailyGoalIdFetchJoinMission(dailyGoalId)) - .willReturn(missions); - - // when - List result = - dailyMissionQueryService.getDailyMissionsByDailyGoal(dailyGoalId); - - // then - assertThat(result.isEmpty()).isFalse(); - } - } -} diff --git a/src/test/java/com/ject/studytrip/mission/application/service/MissionCommandServiceTest.java b/src/test/java/com/ject/studytrip/mission/application/service/MissionCommandServiceTest.java deleted file mode 100644 index d9d563c..0000000 --- a/src/test/java/com/ject/studytrip/mission/application/service/MissionCommandServiceTest.java +++ /dev/null @@ -1,342 +0,0 @@ -package com.ject.studytrip.mission.application.service; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; - -import com.ject.studytrip.BaseUnitTest; -import com.ject.studytrip.global.exception.CustomException; -import com.ject.studytrip.member.domain.model.Member; -import com.ject.studytrip.member.fixture.MemberFixture; -import com.ject.studytrip.mission.domain.error.MissionErrorCode; -import com.ject.studytrip.mission.domain.model.Mission; -import com.ject.studytrip.mission.domain.repository.MissionCommandRepository; -import com.ject.studytrip.mission.domain.repository.MissionRepository; -import com.ject.studytrip.mission.fixture.CreateMissionRequestFixture; -import com.ject.studytrip.mission.fixture.MissionFixture; -import com.ject.studytrip.mission.fixture.UpdateMissionRequestFixture; -import com.ject.studytrip.mission.presentation.dto.request.CreateMissionRequest; -import com.ject.studytrip.mission.presentation.dto.request.UpdateMissionRequest; -import com.ject.studytrip.stamp.domain.model.Stamp; -import com.ject.studytrip.stamp.fixture.StampFixture; -import com.ject.studytrip.trip.domain.model.Trip; -import com.ject.studytrip.trip.domain.model.TripCategory; -import com.ject.studytrip.trip.fixture.TripFixture; -import java.time.LocalDateTime; -import java.util.List; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; - -@DisplayName("MissionCommandService 단위 테스트") -class MissionCommandServiceTest extends BaseUnitTest { - private static final String NEW_MISSION_NAME = "NEW MISSION NAME"; - - @InjectMocks private MissionCommandService missionCommandService; - @Mock private MissionRepository missionRepository; - @Mock private MissionCommandRepository missionCommandRepository; - - private Stamp courseStamp; - private Stamp exploreStamp; - private Mission courseMission; - private Mission exploreMission1; - private Mission exploreMission2; - - @BeforeEach - void setUp() { - Member member = MemberFixture.createMemberFromKakao(); - Trip courseTrip = TripFixture.createTripWithId(1L, member, TripCategory.COURSE); - Trip exploreTrip = TripFixture.createTripWithId(2L, member, TripCategory.EXPLORE); - courseStamp = StampFixture.createStampWithId(1L, courseTrip, 1); - exploreStamp = StampFixture.createStampWithId(2L, exploreTrip, 0); - courseMission = MissionFixture.createMissionWithId(1L, courseStamp); - exploreMission1 = MissionFixture.createMissionWithId(2L, exploreStamp); - exploreMission2 = MissionFixture.createMissionWithId(3L, exploreStamp); - } - - @Nested - @DisplayName("createMission 메서드는") - class CreateMission { - private final CreateMissionRequestFixture fixture = new CreateMissionRequestFixture(); - - @Test - @DisplayName("코스형 여행 스탬프를 위한 미션을 생성하고 반환한다.") - void shouldReturnMissionForCourseStamp() { - // given - CreateMissionRequest request = fixture.build(); - given(missionRepository.save(any(Mission.class))).willReturn(courseMission); - - // when - Mission result = missionCommandService.createMission(courseStamp, request); - - // then - assertThat(result).isEqualTo(courseMission); - assertThat(result.getStamp()).isEqualTo(courseStamp); - } - - @Test - @DisplayName("탐험형 여행 스탬프를 위한 미션을 생성하고 반환한다.") - void shouldReturnMissionForExploreStamp() { - // given - CreateMissionRequest request = fixture.build(); - given(missionRepository.save(any(Mission.class))).willReturn(exploreMission1); - - // when - Mission result = missionCommandService.createMission(exploreStamp, request); - - // then - assertThat(result).isEqualTo(exploreMission1); - assertThat(result.getStamp()).isEqualTo(exploreStamp); - } - } - - @Nested - @DisplayName("updateMissionNameAndMemoIfPresent 메서드는") - class UpdateMissionNameAndMemoIfPresent { - private final UpdateMissionRequestFixture fixture = new UpdateMissionRequestFixture(); - - @Test - @DisplayName("특정 미션의 이름을 수정하고 DB에 반영한다.") - void shouldUpdateMissionName() { - // given - UpdateMissionRequest request = fixture.withName(NEW_MISSION_NAME).build(); - - // when - missionCommandService.updateMissionNameIfPresent(courseMission, request); - - // then - assertThat(courseMission.getName()).isEqualTo(NEW_MISSION_NAME); - } - } - - @Nested - @DisplayName("deleteMission 메서드는") - class DeleteMission { - - @Test - @DisplayName("미션을 삭제하면 deletedAt 필드에 현재 시각이 설정된다.") - void shouldDeleteMission() { - // given - assertThat(courseMission.getDeletedAt()).isNull(); - LocalDateTime beforeDeletionTime = LocalDateTime.now(); - - // when - missionCommandService.deleteMission(courseMission); - - // then - assertThat(courseMission.getDeletedAt()).isNotNull(); - assertThat(courseMission.getDeletedAt()).isAfterOrEqualTo(beforeDeletionTime); - } - } - - @Nested - @DisplayName("completeMission 메서드는") - class CompleteMission { - - @Test - @DisplayName("삭제된 미션이면 예외가 발생한다") - void shouldThrowExceptionWhenMissionIsDeleted() { - // given - exploreMission1.updateDeletedAt(); - - // then - assertThatThrownBy(() -> missionCommandService.completeMission(exploreMission1)) - .isInstanceOf(CustomException.class) - .hasMessage(MissionErrorCode.MISSION_ALREADY_DELETED.getMessage()); - } - - @Test - @DisplayName("이미 완료된 미션이면 예외가 발생한다") - void shouldThrowExceptionWhenMissionIsAlreadyCompleted() { - // given - exploreMission1.updateCompleted(); - - // then - assertThatThrownBy(() -> missionCommandService.completeMission(exploreMission1)) - .isInstanceOf(CustomException.class) - .hasMessage(MissionErrorCode.MISSION_ALREADY_COMPLETED.getMessage()); - } - - @Test - @DisplayName("정상적인 미션이면 completed 필드를 true로 업데이트한다") - void shouldCompletedMission() { - // when - missionCommandService.completeMission(exploreMission1); - - // then - assertThat(exploreMission1.isCompleted()).isTrue(); - } - } - - @Nested - @DisplayName("validateMissionsBelongsToStamp 메서드는") - class ValidateMissionsBelongsToStamp { - - @Test - @DisplayName("미션 목록 중 다른 스탬프에 속한 미션이 존재하면 예외가 발생한다.") - void shouldThrowExceptionWhenMissionNotBelongToStamp() { - // given - Long stampId = exploreStamp.getId(); - List missions = List.of(courseMission, exploreMission1); - - // when & then - assertThatThrownBy( - () -> - missionCommandService.validateMissionsBelongsToStamp( - stampId, missions)) - .isInstanceOf(CustomException.class) - .hasMessage(MissionErrorCode.MISSION_NOT_BELONGS_TO_STAMP.getMessage()); - } - - @Test - @DisplayName("미션 목록이 모두 같은 스탬프에 속한다면 예외가 발생하지 않는다.") - void shouldValidMissionsWhenAllMissionsBelongToStamp() { - // given - Long stampId = exploreStamp.getId(); - List missions = List.of(exploreMission1, exploreMission2); - - // when & then - assertDoesNotThrow( - () -> missionCommandService.validateMissionsBelongsToStamp(stampId, missions)); - } - } - - @Nested - @DisplayName("validateAllMissionsCompletedByStampId 메서드는") - class ValidateAllMissionsCompletedByStampId { - - @Test - @DisplayName("특정 스탬프 하위의 미션이 하나라도 완료되지 않았다면 예외가 발생한다.") - void shouldThrowExceptionWhenAnyMissionIsNotCompleted() { - // given - Long stampId = courseStamp.getId(); - given( - missionCommandRepository - .existsByStampIdAndCompletedIsFalseAndDeletedAtIsNull(stampId)) - .willReturn(true); - - // when & then - assertThatThrownBy( - () -> - missionCommandService.validateAllMissionsCompletedByStampId( - stampId)) - .isInstanceOf(CustomException.class) - .hasMessage(MissionErrorCode.ALL_MISSIONS_NOT_COMPLETED.getMessage()); - } - - @Test - @DisplayName("특정 스탬프 하위의 모든 미션이 완료되면 예외가 발생하지 않는다.") - void shouldPassWhenAllMissionsAreCompleted() { - // given - Long stampId = courseStamp.getId(); - given( - missionCommandRepository - .existsByStampIdAndCompletedIsFalseAndDeletedAtIsNull(stampId)) - .willReturn(false); - - // when & then - assertDoesNotThrow( - () -> missionCommandService.validateAllMissionsCompletedByStampId(stampId)); - } - } - - @Nested - @DisplayName("hardDeleteMissions 메서드는") - class HardDeleteMissions { - - @Test - @DisplayName("삭제된 미션이 없으면 0을 반환한다.") - void shouldReturnZeroWhenDeletedMissionsDoNotExist() { - // given - given(missionCommandRepository.deleteAllByDeletedAtIsNotNull()).willReturn(0L); - - // when - long result = missionCommandService.hardDeleteMissions(); - - // then - assertThat(result).isEqualTo(0L); - } - - @Test - @DisplayName("삭제된 미션이 있으면 해당 개수를 반환한다.") - void shouldReturnCountWhenDeletedMissionsExist() { - // given - given(missionCommandRepository.deleteAllByDeletedAtIsNotNull()).willReturn(5L); - - // when - long result = missionCommandService.hardDeleteMissions(); - - // then - assertThat(result).isEqualTo(5L); - } - } - - @Nested - @DisplayName("hardDeleteMissionsOwnedByDeletedStamp 메서드는") - class HardDeleteMissionsOwnedByDeletedStamp { - - @Test - @DisplayName("삭제된 스탬프가 소유한 미션이 없으면 0을 반환한다.") - void shouldReturnZeroWhenMissionsOwnedByDeletedStampDoNotExist() { - // given - given(missionCommandRepository.deleteAllByDeletedStampOwner()).willReturn(0L); - - // when - long result = missionCommandService.hardDeleteMissionsOwnedByDeletedStamp(); - - // then - assertThat(result).isEqualTo(0L); - } - - @Test - @DisplayName("삭제된 스탬프가 소유한 미션이 있으면 해당 개수를 반환한다.") - void shouldReturnCountWhenMissionsOwnedByDeletedStampExist() { - // given - given(missionCommandRepository.deleteAllByDeletedStampOwner()).willReturn(5L); - - // when - long result = missionCommandService.hardDeleteMissionsOwnedByDeletedStamp(); - - // then - assertThat(result).isEqualTo(5L); - } - } - - @Nested - @DisplayName("hardDeleteMissionsByMember 메서드는") - class HardDeleteMissionsByMember { - - @Test - @DisplayName("특정 멤버가 소유한 미션이 없으면 0을 반환한다.") - void shouldReturnZeroWhenMissionsOwnedByMemberDoNotExist() { - // given - Long memberId = 1L; - given(missionCommandRepository.deleteAllByMemberId(memberId)).willReturn(0L); - - // when - long result = missionCommandService.hardDeleteMissionsByMember(memberId); - - // then - assertThat(result).isEqualTo(0L); - } - - @Test - @DisplayName("특정 멤버가 소유한 미션이 있으면 해당 개수를 반환한다.") - void shouldReturnCountWhenMissionsOwnedByMemberExist() { - // given - Long memberId = 1L; - given(missionCommandRepository.deleteAllByMemberId(memberId)).willReturn(5L); - - // when - long result = missionCommandService.hardDeleteMissionsByMember(memberId); - - // then - assertThat(result).isEqualTo(5L); - } - } -} diff --git a/src/test/java/com/ject/studytrip/mission/application/service/MissionQueryServiceTest.java b/src/test/java/com/ject/studytrip/mission/application/service/MissionQueryServiceTest.java deleted file mode 100644 index 17b9d71..0000000 --- a/src/test/java/com/ject/studytrip/mission/application/service/MissionQueryServiceTest.java +++ /dev/null @@ -1,215 +0,0 @@ -package com.ject.studytrip.mission.application.service; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; - -import com.ject.studytrip.BaseUnitTest; -import com.ject.studytrip.global.exception.CustomException; -import com.ject.studytrip.member.domain.model.Member; -import com.ject.studytrip.member.fixture.MemberFixture; -import com.ject.studytrip.mission.domain.error.MissionErrorCode; -import com.ject.studytrip.mission.domain.model.Mission; -import com.ject.studytrip.mission.domain.repository.MissionQueryRepository; -import com.ject.studytrip.mission.domain.repository.MissionRepository; -import com.ject.studytrip.mission.fixture.MissionFixture; -import com.ject.studytrip.stamp.domain.model.Stamp; -import com.ject.studytrip.stamp.fixture.StampFixture; -import com.ject.studytrip.trip.domain.model.Trip; -import com.ject.studytrip.trip.domain.model.TripCategory; -import com.ject.studytrip.trip.fixture.TripFixture; -import java.util.List; -import java.util.Optional; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; - -@DisplayName("MissionQueryService 단위 테스트") -class MissionQueryServiceTest extends BaseUnitTest { - @InjectMocks private MissionQueryService missionQueryService; - @Mock private MissionRepository missionRepository; - @Mock private MissionQueryRepository missionQueryRepository; - - private Stamp courseStamp; - private Stamp exploreStamp; - private Mission courseMission; - private Mission exploreMission1; - private Mission exploreMission2; - - @BeforeEach - void setUp() { - Member member = MemberFixture.createMemberFromKakao(); - Trip courseTrip = TripFixture.createTripWithId(1L, member, TripCategory.COURSE); - Trip exploreTrip = TripFixture.createTripWithId(2L, member, TripCategory.EXPLORE); - courseStamp = StampFixture.createStampWithId(1L, courseTrip, 1); - exploreStamp = StampFixture.createStampWithId(2L, exploreTrip, 0); - courseMission = MissionFixture.createMissionWithId(1L, courseStamp); - exploreMission1 = MissionFixture.createMissionWithId(2L, exploreStamp); - exploreMission2 = MissionFixture.createMissionWithId(3L, exploreStamp); - } - - @Nested - @DisplayName("getValidMission 메서드는") - class GetValidMission { - - @Test - @DisplayName("미션이 존재하지 않으면 예외가 발생한다.") - void shouldThrowExceptionWhenMissionNotFound() { - // given - Long invalidMissionId = -1L; - Long stampId = exploreStamp.getId(); - given(missionRepository.findById(invalidMissionId)).willReturn(Optional.empty()); - - // when & then - assertThatThrownBy(() -> missionQueryService.getValidMission(stampId, invalidMissionId)) - .isInstanceOf(CustomException.class) - .hasMessage(MissionErrorCode.MISSION_NOT_FOUND.getMessage()); - } - - @Test - @DisplayName("미션이 다른 스탬프에 속하면 예외가 발생한다.") - void shouldThrowExceptionWhenMissionNotBelongToStamp() { - // given - Long missionId = exploreMission1.getId(); - Long invalidStampId = courseStamp.getId(); - given(missionRepository.findById(missionId)).willReturn(Optional.of(exploreMission1)); - - // when & then - assertThatThrownBy(() -> missionQueryService.getValidMission(invalidStampId, missionId)) - .isInstanceOf(CustomException.class) - .hasMessage(MissionErrorCode.MISSION_NOT_BELONGS_TO_STAMP.getMessage()); - } - - @Test - @DisplayName("미션이 이미 삭제된 경우 예외가 발생한다.") - void shouldThrowExceptionWhenMissionIsDeleted() { - // given - Long missionId = exploreMission1.getId(); - Long stampId = exploreStamp.getId(); - exploreMission1.updateDeletedAt(); - given(missionRepository.findById(missionId)).willReturn(Optional.of(exploreMission1)); - - // when & then - assertThatThrownBy(() -> missionQueryService.getValidMission(stampId, missionId)) - .isInstanceOf(CustomException.class) - .hasMessage(MissionErrorCode.MISSION_ALREADY_DELETED.getMessage()); - } - - @Test - @DisplayName("미션이 이미 완료된 경우 예외가 발생한다.") - void shouldThrowExceptionWhenMissionIsCompleted() { - // given - exploreMission1.updateCompleted(); - Long stampId = exploreStamp.getId(); - Long missionId = exploreMission1.getId(); - given(missionRepository.findById(missionId)).willReturn(Optional.of(exploreMission1)); - - // when & then - assertThatThrownBy(() -> missionQueryService.getValidMission(stampId, missionId)) - .isInstanceOf(CustomException.class) - .hasMessage(MissionErrorCode.MISSION_ALREADY_COMPLETED.getMessage()); - } - - @Test - @DisplayName("특정 스탬프에 속하고 삭제되지 않은 미션이 존재하면, 해당 미션을 반환한다.") - void shouldReturnValidMission() { - // given - Long missionId = exploreMission1.getId(); - Long stampId = exploreStamp.getId(); - given(missionRepository.findById(missionId)).willReturn(Optional.of(exploreMission1)); - - // when - Mission result = missionQueryService.getValidMission(stampId, missionId); - - // then - assertThat(result).isEqualTo(exploreMission1); - } - } - - @Nested - @DisplayName("getMissionsByStampId 메서드는") - class GetMissionsByStampId { - - @Test - @DisplayName("특정 스탬프에 대한 삭제되지 않은 모든 미션을 생성일 순으로 반환한다.") - void shouldReturnMissionsInOrderWhenStampIdExists() { - // given - Long stampId = exploreStamp.getId(); - given(missionRepository.findAllByStampIdAndDeletedAtIsNullOrderByCreatedAt(stampId)) - .willReturn(List.of(exploreMission1, exploreMission2)); - - // when - List result = missionQueryService.getMissionsByStampId(stampId); - - // then - assertThat(result).hasSize(2); - assertThat(result).containsExactly(exploreMission1, exploreMission2); - } - } - - @Nested - @DisplayName("getValidMissionsWithStamp 메서드는") - class GetValidMissionsWithStamp { - - @Test - @DisplayName("요청한 ID 개수와 조회된 미션 개수가 다르면 예외가 발생한다") - void shouldThrowExceptionWhenSomeMissionsDoNotExist() { - // given - List missionIds = List.of(1L, 2L); - given(missionQueryRepository.findAllByIdsInFetchJoinStamp(missionIds)) - .willReturn(List.of(courseMission)); - - // when & then - assertThatThrownBy(() -> missionQueryService.getValidMissionsWithStamp(missionIds)) - .isInstanceOf(CustomException.class) - .hasMessage(MissionErrorCode.MISSION_NOT_FOUND.getMessage()); - } - - @Test - @DisplayName("이미 삭제된 미션이 포함되어 있을 경우 예외가 발생한다") - void shouldThrowExceptionWhenAnyMissionIsDeleted() { - // given - courseMission.updateDeletedAt(); // deleted - given(missionQueryRepository.findAllByIdsInFetchJoinStamp(any())) - .willReturn(List.of(courseMission)); - - // when & then - assertThatThrownBy(() -> missionQueryService.getValidMissionsWithStamp(List.of(1L))) - .isInstanceOf(CustomException.class) - .hasMessage(MissionErrorCode.MISSION_ALREADY_DELETED.getMessage()); - } - - @Test - @DisplayName("이미 완료된 미션이 포함되어 있을 경우 예외가 발생한다") - void shouldThrowExceptionWhenAnyMissionIsCompleted() { - // given - courseMission.updateCompleted(); - given(missionQueryRepository.findAllByIdsInFetchJoinStamp(any())) - .willReturn(List.of(courseMission)); - - // when & then - assertThatThrownBy(() -> missionQueryService.getValidMissionsWithStamp(List.of(1L))) - .isInstanceOf(CustomException.class) - .hasMessage(MissionErrorCode.MISSION_ALREADY_COMPLETED.getMessage()); - } - - @Test - @DisplayName("유효한 미션 ID들로 요청 시 검증을 통과하고 미션 리스트를 반환한다") - void shouldReturnValidMissions() { - // given - List missionIds = List.of(courseMission.getId()); - given(missionQueryRepository.findAllByIdsInFetchJoinStamp(missionIds)) - .willReturn(List.of(courseMission)); - - // when - List result = missionQueryService.getValidMissionsWithStamp(missionIds); - - // then - assertThat(result).containsExactly(courseMission); - } - } -} diff --git a/src/test/java/com/ject/studytrip/mission/fixture/CreateMissionRequestFixture.java b/src/test/java/com/ject/studytrip/mission/fixture/CreateMissionRequestFixture.java deleted file mode 100644 index b992d3f..0000000 --- a/src/test/java/com/ject/studytrip/mission/fixture/CreateMissionRequestFixture.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.ject.studytrip.mission.fixture; - -import com.ject.studytrip.mission.presentation.dto.request.CreateMissionRequest; - -public class CreateMissionRequestFixture { - private String name = "TEST MISSION NAME"; - - public CreateMissionRequestFixture withName(String name) { - this.name = name; - return this; - } - - public CreateMissionRequest build() { - return new CreateMissionRequest(name); - } -} diff --git a/src/test/java/com/ject/studytrip/mission/fixture/DailyMissionFixture.java b/src/test/java/com/ject/studytrip/mission/fixture/DailyMissionFixture.java deleted file mode 100644 index 24aaf9a..0000000 --- a/src/test/java/com/ject/studytrip/mission/fixture/DailyMissionFixture.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.ject.studytrip.mission.fixture; - -import com.ject.studytrip.mission.domain.factory.DailyMissionFactory; -import com.ject.studytrip.mission.domain.model.DailyMission; -import com.ject.studytrip.mission.domain.model.Mission; -import com.ject.studytrip.trip.domain.model.DailyGoal; -import org.springframework.test.util.ReflectionTestUtils; - -public class DailyMissionFixture { - - public static DailyMission createDailyMission(Mission mission, DailyGoal dailyGoal) { - return DailyMissionFactory.create(mission, dailyGoal); - } - - public static DailyMission createDailyMissionWithId( - Long id, Mission mission, DailyGoal dailyGoal) { - DailyMission dailyMission = DailyMissionFactory.create(mission, dailyGoal); - ReflectionTestUtils.setField(dailyMission, "id", id); - - return dailyMission; - } -} diff --git a/src/test/java/com/ject/studytrip/mission/fixture/MissionFixture.java b/src/test/java/com/ject/studytrip/mission/fixture/MissionFixture.java deleted file mode 100644 index fa5b12a..0000000 --- a/src/test/java/com/ject/studytrip/mission/fixture/MissionFixture.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.ject.studytrip.mission.fixture; - -import com.ject.studytrip.mission.domain.factory.MissionFactory; -import com.ject.studytrip.mission.domain.model.Mission; -import com.ject.studytrip.stamp.domain.model.Stamp; -import org.springframework.test.util.ReflectionTestUtils; - -public class MissionFixture { - private static final String MISSION_NAME = "TEST MISSION NAME"; - - public static Mission createMission(Stamp stamp) { - return MissionFactory.create(stamp, MISSION_NAME); - } - - public static Mission createMissionWithId(Long id, Stamp stamp) { - Mission mission = MissionFactory.create(stamp, MISSION_NAME); - ReflectionTestUtils.setField(mission, "id", id); - - return mission; - } -} diff --git a/src/test/java/com/ject/studytrip/mission/fixture/UpdateMissionRequestFixture.java b/src/test/java/com/ject/studytrip/mission/fixture/UpdateMissionRequestFixture.java deleted file mode 100644 index e009bce..0000000 --- a/src/test/java/com/ject/studytrip/mission/fixture/UpdateMissionRequestFixture.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.ject.studytrip.mission.fixture; - -import com.ject.studytrip.mission.presentation.dto.request.UpdateMissionRequest; - -public class UpdateMissionRequestFixture { - private String name = null; - - public UpdateMissionRequestFixture withName(String name) { - this.name = name; - return this; - } - - public UpdateMissionRequest build() { - return new UpdateMissionRequest(name); - } -} diff --git a/src/test/java/com/ject/studytrip/mission/helper/DailyMissionTestHelper.java b/src/test/java/com/ject/studytrip/mission/helper/DailyMissionTestHelper.java deleted file mode 100644 index 71f5a1c..0000000 --- a/src/test/java/com/ject/studytrip/mission/helper/DailyMissionTestHelper.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.ject.studytrip.mission.helper; - -import com.ject.studytrip.mission.domain.model.DailyMission; -import com.ject.studytrip.mission.domain.model.Mission; -import com.ject.studytrip.mission.domain.repository.DailyMissionRepository; -import com.ject.studytrip.mission.fixture.DailyMissionFixture; -import com.ject.studytrip.trip.domain.model.DailyGoal; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -@Component -public class DailyMissionTestHelper { - - @Autowired private DailyMissionRepository dailyMissionRepository; - - public DailyMission saveDailyMission(Mission mission, DailyGoal dailyGoal) { - DailyMission dailyMission = DailyMissionFixture.createDailyMission(mission, dailyGoal); - return dailyMissionRepository.save(dailyMission); - } - - public DailyMission saveDeletedDailyMission(Mission mission, DailyGoal dailyGoal) { - DailyMission dailyMission = DailyMissionFixture.createDailyMission(mission, dailyGoal); - dailyMission.updateDeletedAt(); - - return dailyMissionRepository.save(dailyMission); - } -} diff --git a/src/test/java/com/ject/studytrip/mission/helper/MissionTestHelper.java b/src/test/java/com/ject/studytrip/mission/helper/MissionTestHelper.java deleted file mode 100644 index 88dd3df..0000000 --- a/src/test/java/com/ject/studytrip/mission/helper/MissionTestHelper.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.ject.studytrip.mission.helper; - -import com.ject.studytrip.mission.domain.model.Mission; -import com.ject.studytrip.mission.domain.repository.MissionRepository; -import com.ject.studytrip.mission.fixture.MissionFixture; -import com.ject.studytrip.stamp.domain.model.Stamp; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -@Component -public class MissionTestHelper { - @Autowired private MissionRepository missionRepository; - - public Mission saveMission(Stamp stamp) { - Mission mission = MissionFixture.createMission(stamp); - - return missionRepository.save(mission); - } - - public Mission saveDeletedMission(Stamp stamp) { - Mission mission = MissionFixture.createMission(stamp); - mission.updateDeletedAt(); - - return missionRepository.save(mission); - } - - public Mission saveCompletedMission(Stamp stamp) { - Mission mission = MissionFixture.createMission(stamp); - mission.updateCompleted(); - - return missionRepository.save(mission); - } -} diff --git a/src/test/java/com/ject/studytrip/mission/presentation/controller/MissionControllerIntegrationTest.java b/src/test/java/com/ject/studytrip/mission/presentation/controller/MissionControllerIntegrationTest.java deleted file mode 100644 index 4fbf33d..0000000 --- a/src/test/java/com/ject/studytrip/mission/presentation/controller/MissionControllerIntegrationTest.java +++ /dev/null @@ -1,1278 +0,0 @@ -package com.ject.studytrip.mission.presentation.controller; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -import com.ject.studytrip.BaseIntegrationTest; -import com.ject.studytrip.auth.domain.error.AuthErrorCode; -import com.ject.studytrip.auth.fixture.TokenFixture; -import com.ject.studytrip.auth.helper.TokenTestHelper; -import com.ject.studytrip.global.exception.error.CommonErrorCode; -import com.ject.studytrip.member.domain.model.Member; -import com.ject.studytrip.member.helper.MemberTestHelper; -import com.ject.studytrip.mission.domain.error.MissionErrorCode; -import com.ject.studytrip.mission.domain.model.Mission; -import com.ject.studytrip.mission.fixture.CreateMissionRequestFixture; -import com.ject.studytrip.mission.fixture.UpdateMissionRequestFixture; -import com.ject.studytrip.mission.helper.MissionTestHelper; -import com.ject.studytrip.mission.presentation.dto.request.CreateMissionRequest; -import com.ject.studytrip.mission.presentation.dto.request.UpdateMissionRequest; -import com.ject.studytrip.stamp.domain.error.StampErrorCode; -import com.ject.studytrip.stamp.domain.model.Stamp; -import com.ject.studytrip.stamp.helper.StampTestHelper; -import com.ject.studytrip.trip.domain.error.TripErrorCode; -import com.ject.studytrip.trip.domain.model.Trip; -import com.ject.studytrip.trip.domain.model.TripCategory; -import com.ject.studytrip.trip.helper.TripTestHelper; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.ResultActions; - -@DisplayName("MissionController 통합 테스트") -class MissionControllerIntegrationTest extends BaseIntegrationTest { - private static final String BASE_MISSION_URL = "/api/trips/{tripId}/stamps/{stampId}/missions"; - - @Autowired private MemberTestHelper memberTestHelper; - @Autowired private TokenTestHelper tokenTestHelper; - @Autowired private TripTestHelper tripTestHelper; - @Autowired private StampTestHelper stampTestHelper; - @Autowired private MissionTestHelper missionTestHelper; - - private String accessToken; - private Trip courseTrip; - private Trip exploreTrip; - private Stamp courseStamp; - private Stamp exploreStamp; - private Mission courseMission1; - private Mission courseMission2; - private Mission exploreMission1; - private Mission exploreMission2; - - private String newAccessToken; - private Stamp newStamp; - private Mission newMission; - - @BeforeEach - void setUp() { - Member member = memberTestHelper.saveMember(); - accessToken = - tokenTestHelper.createAccessToken( - member.getId().toString(), member.getRole().name()); - courseTrip = tripTestHelper.saveTrip(member, TripCategory.COURSE); - exploreTrip = tripTestHelper.saveTrip(member, TripCategory.EXPLORE); - courseStamp = stampTestHelper.saveStamp(courseTrip, 3); - exploreStamp = stampTestHelper.saveStamp(exploreTrip, 0); - courseMission1 = missionTestHelper.saveMission(courseStamp); - courseMission2 = missionTestHelper.saveMission(courseStamp); - exploreMission1 = missionTestHelper.saveMission(exploreStamp); - exploreMission2 = missionTestHelper.saveMission(exploreStamp); - - Member newMember = memberTestHelper.saveMember("test@kakao.com", "TEST NICKNAME"); - newAccessToken = - tokenTestHelper.createAccessToken( - newMember.getId().toString(), newMember.getRole().name()); - Trip newTrip = tripTestHelper.saveTrip(newMember, TripCategory.EXPLORE); - newStamp = stampTestHelper.saveStamp(newTrip, 0); - newMission = missionTestHelper.saveMission(newStamp); - } - - @Nested - @DisplayName("미션 생성 API") - class CreateMission { - private final CreateMissionRequestFixture fixture = new CreateMissionRequestFixture(); - - private ResultActions getResultActions( - String accessToken, Object tripId, Object stampId, CreateMissionRequest request) - throws Exception { - return mockMvc.perform( - post(BASE_MISSION_URL, tripId, stampId) - .header( - HttpHeaders.AUTHORIZATION, - TokenFixture.TOKEN_PREFIX + accessToken) - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(request))); - } - - @Test - @DisplayName("Access Token이 없으면 401 Unauthorized를 반환한다.") - void shouldReturnUnauthorizedWhenAccessTokenIsMissing() throws Exception { - // given - CreateMissionRequest request = fixture.build(); - - // when - ResultActions resultActions = - getResultActions("", courseTrip.getId(), courseStamp.getId(), request); - - // then - resultActions - .andExpect(status().isUnauthorized()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value(AuthErrorCode.UNAUTHENTICATED.getStatus().value())); - } - - @Test - @DisplayName("PathVariable 여행 ID 타입이 올바르지 않으면 400 Bad Request를 반환한다.") - void shouldReturnBadRequestWhenTripIdTypeMismatch() throws Exception { - // given - String invalidTripId = "abc"; - CreateMissionRequest request = fixture.build(); - - // when - ResultActions resultActions = - getResultActions(accessToken, invalidTripId, courseStamp.getId(), request); - - // then - resultActions - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value( - CommonErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH - .getStatus() - .value())); - } - - @Test - @DisplayName("PathVariable 스탬프 ID 타입이 올바르지 않으면 400 Bad Request를 반환한다.") - void shouldReturnBadRequestWhenStampIdTypeMismatch() throws Exception { - // given - String invalidStampId = "def"; - CreateMissionRequest request = fixture.build(); - - // when - ResultActions resultActions = - getResultActions(accessToken, courseTrip.getId(), invalidStampId, request); - - // then - resultActions - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value( - CommonErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH - .getStatus() - .value())); - } - - @Test - @DisplayName("미션 이름이 null 이면 400 Bad Request를 반환한다.") - void shouldReturnBadRequestWhenNameIsNull() throws Exception { - // given - CreateMissionRequest request = fixture.withName(null).build(); - - // when - ResultActions resultActions = - getResultActions(accessToken, courseTrip.getId(), courseStamp.getId(), request); - - // then - resultActions - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value( - CommonErrorCode.METHOD_ARGUMENT_NOT_VALID - .getStatus() - .value())); - } - - @Test - @DisplayName("미션 이름이 비어있으면 400 Bad Request를 반환한다.") - void shouldReturnBadRequestWhenNameIsBlank() throws Exception { - // given - CreateMissionRequest request = fixture.withName(" ").build(); - - // when - ResultActions resultActions = - getResultActions(accessToken, courseTrip.getId(), courseStamp.getId(), request); - - // then - resultActions - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value( - CommonErrorCode.METHOD_ARGUMENT_NOT_VALID - .getStatus() - .value())); - } - - @Test - @DisplayName("삭제된 여행일 경우 400 Bad Request를 반환한다.") - void shouldReturnBadRequestWhenTripAlreadyDeleted() throws Exception { - // given - courseTrip.updateDeletedAt(); - CreateMissionRequest request = fixture.build(); - - // when - ResultActions resultActions = - getResultActions(accessToken, courseTrip.getId(), courseStamp.getId(), request); - - // then - resultActions - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value(TripErrorCode.TRIP_ALREADY_DELETED.getStatus().value())); - } - - @Test - @DisplayName("삭제된 스탬프일 경우 400 Bad Request를 반환한다.") - void shouldReturnBadRequestWhenStampAlreadyDeleted() throws Exception { - // given - courseStamp.updateDeletedAt(); - CreateMissionRequest request = fixture.build(); - - // when - ResultActions resultActions = - getResultActions(accessToken, courseTrip.getId(), courseStamp.getId(), request); - - // then - resultActions - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value( - StampErrorCode.STAMP_ALREADY_DELETED - .getStatus() - .value())); - } - - @Test - @DisplayName("여행의 소유자가 아니라면 403 Forbidden을 반환한다.") - void shouldReturnForbiddenWhenNotTripOwner() throws Exception { - // given - CreateMissionRequest request = fixture.build(); - - // when - ResultActions resultActions = - getResultActions(newAccessToken, courseTrip.getId(), newStamp.getId(), request); - - // then - resultActions - .andExpect(status().isForbidden()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value(TripErrorCode.NOT_TRIP_OWNER.getStatus().value())); - } - - @Test - @DisplayName("스탬프가 요청한 여행에 속하지 않으면 403 Forbidden을 반환한다.") - void shouldReturnForbiddenWhenStampNotBelongToTrip() throws Exception { - // given - CreateMissionRequest request = fixture.build(); - - // when - ResultActions resultActions = - getResultActions( - accessToken, courseTrip.getId(), exploreStamp.getId(), request); - - // then - resultActions - .andExpect(status().isForbidden()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value( - StampErrorCode.STAMP_NOT_BELONG_TO_TRIP - .getStatus() - .value())); - } - - @Test - @DisplayName("유효하지 않은 여행 ID가 들어오면 404 Not Found를 반환한다.") - void shouldReturnNotFoundWhenTripIdIsInvalid() throws Exception { - // given - Long invalidTripId = 10000L; - CreateMissionRequest request = fixture.build(); - - // when - ResultActions resultActions = - getResultActions(accessToken, invalidTripId, courseStamp.getId(), request); - - // when & then - resultActions - .andExpect(status().isNotFound()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value(TripErrorCode.TRIP_NOT_FOUND.getStatus().value())); - } - - @Test - @DisplayName("유효하지 않은 스탬프 ID가 들어오면 404 Not Found를 반환한다.") - void shouldReturnNotFoundWhenStampIdIsInvalid() throws Exception { - // given - Long invalidStampId = 10000L; - CreateMissionRequest request = fixture.build(); - - // when - ResultActions resultActions = - getResultActions(accessToken, courseTrip.getId(), invalidStampId, request); - - // when & then - resultActions - .andExpect(status().isNotFound()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value(StampErrorCode.STAMP_NOT_FOUND.getStatus().value())); - } - - @Test - @DisplayName("유효한 요청이 들어오면 미션을 생성한다.") - void shouldCreateMissionWhenRequestIsValid() throws Exception { - // given - CreateMissionRequest request = fixture.build(); - - // when - ResultActions resultActions = - getResultActions(accessToken, courseTrip.getId(), courseStamp.getId(), request); - - // then - resultActions - .andExpect(status().isCreated()) - .andExpect(jsonPath("$.success").value(true)) - .andExpect(jsonPath("$.status").value(HttpStatus.CREATED.value())) - .andExpect(jsonPath("$.data.missionId").isNumber()); - } - } - - @Nested - @DisplayName("미션 수정 API") - class UpdateMission { - private final UpdateMissionRequestFixture fixture = new UpdateMissionRequestFixture(); - - private ResultActions getResultActions( - String accessToken, - Object tripId, - Object stampId, - Object missionId, - UpdateMissionRequest request) - throws Exception { - return mockMvc.perform( - patch(BASE_MISSION_URL + "/{missionId}", tripId, stampId, missionId) - .header( - HttpHeaders.AUTHORIZATION, - TokenFixture.TOKEN_PREFIX + accessToken) - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(request))); - } - - @Test - @DisplayName("Access Token이 없으면 401 Unauthorized를 반환한다.") - void shouldReturnUnauthorizedWhenAccessTokenIsMissing() throws Exception { - // given - UpdateMissionRequest request = fixture.withName("새로운 미션 이름").build(); - - // when - ResultActions resultActions = - getResultActions( - "", - courseTrip.getId(), - courseStamp.getId(), - courseMission1.getId(), - request); - - // then - resultActions - .andExpect(status().isUnauthorized()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value(AuthErrorCode.UNAUTHENTICATED.getStatus().value())); - } - - @Test - @DisplayName("PathVariable 여행 ID 타입이 올바르지 않으면 400 Bad Request를 반환한다.") - void shouldReturnBadRequestWhenTripIdTypeMismatch() throws Exception { - // given - String invalidTripId = "abc"; - UpdateMissionRequest request = fixture.withName("새로운 미션 이름").build(); - - // when - ResultActions resultActions = - getResultActions( - accessToken, - invalidTripId, - courseStamp.getId(), - courseMission1.getId(), - request); - - // then - resultActions - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value( - CommonErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH - .getStatus() - .value())); - } - - @Test - @DisplayName("PathVariable 스탬프 ID 타입이 올바르지 않으면 400 Bad Request를 반환한다.") - void shouldReturnBadRequestWhenStampIdTypeMismatch() throws Exception { - // given - String invalidStampId = "def"; - UpdateMissionRequest request = fixture.withName("새로운 미션 이름").build(); - - // when - ResultActions resultActions = - getResultActions( - accessToken, - courseTrip.getId(), - invalidStampId, - courseMission1.getId(), - request); - - // then - resultActions - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value( - CommonErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH - .getStatus() - .value())); - } - - @Test - @DisplayName("PathVariable 미션 ID 타입이 올바르지 않으면 400 Bad Request를 반환한다.") - void shouldReturnBadRequestWhenMissionIdTypeMismatch() throws Exception { - // given - String invalidMissionId = "ghi"; - UpdateMissionRequest request = fixture.withName("새로운 미션 이름").build(); - - // when - ResultActions resultActions = - getResultActions( - accessToken, - courseTrip.getId(), - courseStamp.getId(), - invalidMissionId, - request); - - // then - resultActions - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value( - CommonErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH - .getStatus() - .value())); - } - - @Test - @DisplayName("삭제된 여행일 경우 400 Bad Request를 반환한다.") - void shouldReturnBadRequestWhenTripAlreadyDeleted() throws Exception { - // given - courseTrip.updateDeletedAt(); - UpdateMissionRequest request = fixture.withName("새로운 미션 이름").build(); - - // when - ResultActions resultActions = - getResultActions( - accessToken, - courseTrip.getId(), - courseStamp.getId(), - courseMission1.getId(), - request); - - // then - resultActions - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value(TripErrorCode.TRIP_ALREADY_DELETED.getStatus().value())); - } - - @Test - @DisplayName("삭제된 스탬프일 경우 400 Bad Request를 반환한다.") - void shouldReturnBadRequestWhenStampAlreadyDeleted() throws Exception { - // given - courseStamp.updateDeletedAt(); - UpdateMissionRequest request = fixture.withName("새로운 미션 이름").build(); - - // when - ResultActions resultActions = - getResultActions( - accessToken, - courseTrip.getId(), - courseStamp.getId(), - courseMission1.getId(), - request); - - // then - resultActions - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value( - StampErrorCode.STAMP_ALREADY_DELETED - .getStatus() - .value())); - } - - @Test - @DisplayName("삭제된 미션일 경우 400 Bad Request를 반환한다.") - void shouldReturnBadRequestWhenMissionAlreadyDeleted() throws Exception { - // given - courseMission1.updateDeletedAt(); - UpdateMissionRequest request = fixture.withName("새로운 미션 이름").build(); - - // when - ResultActions resultActions = - getResultActions( - accessToken, - courseTrip.getId(), - courseStamp.getId(), - courseMission1.getId(), - request); - - // then - resultActions - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value( - MissionErrorCode.MISSION_ALREADY_DELETED - .getStatus() - .value())); - } - - @Test - @DisplayName("여행의 소유자가 아니라면 403 Forbidden을 반환한다.") - void shouldReturnForbiddenWhenNotTripOwner() throws Exception { - // given - UpdateMissionRequest request = fixture.withName("새로운 미션 이름").build(); - - // when - ResultActions resultActions = - getResultActions( - newAccessToken, - courseTrip.getId(), - newStamp.getId(), - newMission.getId(), - request); - - // then - resultActions - .andExpect(status().isForbidden()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value(TripErrorCode.NOT_TRIP_OWNER.getStatus().value())); - } - - @Test - @DisplayName("스탬프가 요청한 여행에 속하지 않으면 403 Forbidden을 반환한다.") - void shouldReturnForbiddenWhenStampNotBelongToTrip() throws Exception { - // given - UpdateMissionRequest request = fixture.withName("새로운 미션 이름").build(); - - // when - ResultActions resultActions = - getResultActions( - accessToken, - courseTrip.getId(), - exploreStamp.getId(), - exploreMission1.getId(), - request); - - // then - resultActions - .andExpect(status().isForbidden()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value( - StampErrorCode.STAMP_NOT_BELONG_TO_TRIP - .getStatus() - .value())); - } - - @Test - @DisplayName("미션이 요청한 스탬프에 속하지 않으면 403 Forbidden을 반환한다.") - void shouldReturnForbiddenWhenMissionNotBelongToStamp() throws Exception { - // given - UpdateMissionRequest request = fixture.withName("새로운 미션 이름").build(); - - // when - ResultActions resultActions = - getResultActions( - accessToken, - courseTrip.getId(), - courseStamp.getId(), - exploreMission1.getId(), - request); - - // then - resultActions - .andExpect(status().isForbidden()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value( - MissionErrorCode.MISSION_NOT_BELONGS_TO_STAMP - .getStatus() - .value())); - } - - @Test - @DisplayName("유효하지 않은 여행 ID가 들어오면 404 Not Found를 반환한다.") - void shouldReturnNotFoundWhenTripIdIsInvalid() throws Exception { - // given - Long invalidTripId = 10000L; - UpdateMissionRequest request = fixture.withName("새로운 미션 이름").build(); - - // when - ResultActions resultActions = - getResultActions( - accessToken, - invalidTripId, - courseStamp.getId(), - courseMission1.getId(), - request); - - // when & then - resultActions - .andExpect(status().isNotFound()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value(TripErrorCode.TRIP_NOT_FOUND.getStatus().value())); - } - - @Test - @DisplayName("유효하지 않은 스탬프 ID가 들어오면 404 Not Found를 반환한다.") - void shouldReturnNotFoundWhenStampIdIsInvalid() throws Exception { - // given - Long invalidStampId = 10000L; - UpdateMissionRequest request = fixture.withName("새로운 미션 이름").build(); - - // when - ResultActions resultActions = - getResultActions( - accessToken, - courseTrip.getId(), - invalidStampId, - courseMission1.getId(), - request); - - // when & then - resultActions - .andExpect(status().isNotFound()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value(StampErrorCode.STAMP_NOT_FOUND.getStatus().value())); - } - - @Test - @DisplayName("유효하지 않은 미션 ID가 들어오면 404 Not Found를 반환한다.") - void shouldReturnNotFoundWhenMissionIdIsInvalid() throws Exception { - // given - Long invalidMissionId = 10000L; - UpdateMissionRequest request = fixture.withName("새로운 미션 이름").build(); - - // when - ResultActions resultActions = - getResultActions( - accessToken, - courseTrip.getId(), - courseStamp.getId(), - invalidMissionId, - request); - - // when & then - resultActions - .andExpect(status().isNotFound()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value(MissionErrorCode.MISSION_NOT_FOUND.getStatus().value())); - } - - @Test - @DisplayName("유효한 요청이 들어오면 미션 이름을 수정한다.") - void shouldUpdateMissionNameWhenRequestIsValid() throws Exception { - // given - UpdateMissionRequest request = fixture.withName("새로운 미션 이름").build(); - - // when - ResultActions resultActions = - getResultActions( - accessToken, - courseTrip.getId(), - courseStamp.getId(), - courseMission1.getId(), - request); - - // then - resultActions - .andExpect(status().isOk()) - .andExpect(jsonPath("$.success").value(true)) - .andExpect(jsonPath("$.status").value(HttpStatus.OK.value())); - } - } - - @Nested - @DisplayName("미션 삭제 API") - class DeleteMission { - private ResultActions getResultActions( - String accessToken, Object tripId, Object stampId, Object missionId) - throws Exception { - return mockMvc.perform( - delete(BASE_MISSION_URL + "/{missionId}", tripId, stampId, missionId) - .header( - HttpHeaders.AUTHORIZATION, - TokenFixture.TOKEN_PREFIX + accessToken)); - } - - @Test - @DisplayName("Access Token이 없으면 401 Unauthorized를 반환한다.") - void shouldReturnUnauthorizedWhenAccessTokenIsMissing() throws Exception { - // when - ResultActions resultActions = - getResultActions( - "", exploreTrip.getId(), exploreStamp.getId(), exploreMission1.getId()); - - // then - resultActions - .andExpect(status().isUnauthorized()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value(AuthErrorCode.UNAUTHENTICATED.getStatus().value())); - } - - @Test - @DisplayName("PathVariable 여행 ID 타입이 올바르지 않으면 400 Bad Request를 반환한다.") - void shouldReturnBadRequestWhenTripIdTypeMismatch() throws Exception { - // given - String invalidTripId = "abc"; - - // when - ResultActions resultActions = - getResultActions( - accessToken, - invalidTripId, - exploreStamp.getId(), - exploreMission1.getId()); - - // then - resultActions - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value( - CommonErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH - .getStatus() - .value())); - } - - @Test - @DisplayName("PathVariable 스탬프 ID 타입이 올바르지 않으면 400 Bad Request를 반환한다.") - void shouldReturnBadRequestWhenStampIdTypeMismatch() throws Exception { - // given - String invalidStampId = "def"; - - // when - ResultActions resultActions = - getResultActions( - accessToken, - exploreTrip.getId(), - invalidStampId, - exploreMission1.getId()); - - // then - resultActions - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value( - CommonErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH - .getStatus() - .value())); - } - - @Test - @DisplayName("PathVariable 미션 ID 타입이 올바르지 않으면 400 Bad Request를 반환한다.") - void shouldReturnBadRequestWhenMissionIdTypeMismatch() throws Exception { - // given - String invalidMissionId = "ghi"; - - // when - ResultActions resultActions = - getResultActions( - accessToken, - exploreTrip.getId(), - exploreStamp.getId(), - invalidMissionId); - - // then - resultActions - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value( - CommonErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH - .getStatus() - .value())); - } - - @Test - @DisplayName("삭제된 여행일 경우 400 Bad Request를 반환한다.") - void shouldReturnBadRequestWhenTripAlreadyDeleted() throws Exception { - // given - courseTrip.updateDeletedAt(); - - // when - ResultActions resultActions = - getResultActions( - accessToken, - courseTrip.getId(), - courseStamp.getId(), - courseMission2.getId()); - - // then - resultActions - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value(TripErrorCode.TRIP_ALREADY_DELETED.getStatus().value())); - } - - @Test - @DisplayName("삭제된 스탬프일 경우 400 Bad Request를 반환한다.") - void shouldReturnBadRequestWhenStampAlreadyDeleted() throws Exception { - // given - courseStamp.updateDeletedAt(); - - // when - ResultActions resultActions = - getResultActions( - accessToken, - courseTrip.getId(), - courseStamp.getId(), - courseMission2.getId()); - - // then - resultActions - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value( - StampErrorCode.STAMP_ALREADY_DELETED - .getStatus() - .value())); - } - - @Test - @DisplayName("삭제된 미션일 경우 400 Bad Request를 반환한다.") - void shouldReturnBadRequestWhenMissionAlreadyDeleted() throws Exception { - // given - courseMission1.updateDeletedAt(); - - // when - ResultActions resultActions = - getResultActions( - accessToken, - courseTrip.getId(), - courseStamp.getId(), - courseMission1.getId()); - - // then - resultActions - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value( - MissionErrorCode.MISSION_ALREADY_DELETED - .getStatus() - .value())); - } - - @Test - @DisplayName("여행의 소유자가 아니라면 403 Forbidden을 반환한다.") - void shouldReturnForbiddenWhenNotTripOwner() throws Exception { - // when - ResultActions resultActions = - getResultActions( - newAccessToken, - exploreTrip.getId(), - newStamp.getId(), - newMission.getId()); - - // then - resultActions - .andExpect(status().isForbidden()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value(TripErrorCode.NOT_TRIP_OWNER.getStatus().value())); - } - - @Test - @DisplayName("스탬프가 요청한 여행에 속하지 않으면 403 Forbidden을 반환한다.") - void shouldReturnForbiddenWhenStampNotBelongToTrip() throws Exception { - // when - ResultActions resultActions = - getResultActions( - accessToken, - courseTrip.getId(), - exploreStamp.getId(), - exploreMission1.getId()); - - // then - resultActions - .andExpect(status().isForbidden()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value( - StampErrorCode.STAMP_NOT_BELONG_TO_TRIP - .getStatus() - .value())); - } - - @Test - @DisplayName("미션이 요청한 스탬프에 속하지 않으면 403 Forbidden을 반환한다.") - void shouldReturnForbiddenWhenMissionNotBelongToStamp() throws Exception { - // when - ResultActions resultActions = - getResultActions( - accessToken, - courseTrip.getId(), - courseStamp.getId(), - exploreMission1.getId()); - - // then - resultActions - .andExpect(status().isForbidden()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value( - MissionErrorCode.MISSION_NOT_BELONGS_TO_STAMP - .getStatus() - .value())); - } - - @Test - @DisplayName("유효하지 않은 여행 ID가 들어오면 404 Not Found를 반환한다.") - void shouldReturnNotFoundWhenTripIdIsInvalid() throws Exception { - // given - Long invalidTripId = 10000L; - // when - ResultActions resultActions = - getResultActions( - accessToken, - invalidTripId, - exploreStamp.getId(), - exploreMission2.getId()); - - // when & then - resultActions - .andExpect(status().isNotFound()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value(TripErrorCode.TRIP_NOT_FOUND.getStatus().value())); - } - - @Test - @DisplayName("유효하지 않은 스탬프 ID가 들어오면 404 Not Found를 반환한다.") - void shouldReturnNotFoundWhenStampIdIsInvalid() throws Exception { - // given - Long invalidStampId = 10000L; - - // when - ResultActions resultActions = - getResultActions( - accessToken, - exploreTrip.getId(), - invalidStampId, - exploreMission2.getId()); - - // when & then - resultActions - .andExpect(status().isNotFound()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value(StampErrorCode.STAMP_NOT_FOUND.getStatus().value())); - } - - @Test - @DisplayName("유효하지 않은 미션 ID가 들어오면 404 Not Found를 반환한다.") - void shouldReturnNotFoundWhenMissionIdIsInvalid() throws Exception { - // given - Long invalidMissionId = 10000L; - - // when - ResultActions resultActions = - getResultActions( - accessToken, - exploreTrip.getId(), - exploreStamp.getId(), - invalidMissionId); - - // when & then - resultActions - .andExpect(status().isNotFound()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value(MissionErrorCode.MISSION_NOT_FOUND.getStatus().value())); - } - - @Test - @DisplayName("유효한 요청이 들어오면 미션을 삭제한다.") - void shouldDeleteMissionWhenRequestIsValid() throws Exception { - // when - ResultActions resultActions = - getResultActions( - accessToken, - exploreTrip.getId(), - exploreStamp.getId(), - exploreMission2.getId()); - - // then - resultActions - .andExpect(status().isOk()) - .andExpect(jsonPath("$.success").value(true)) - .andExpect(jsonPath("$.status").value(HttpStatus.OK.value())); - } - } - - @Nested - @DisplayName("미션 목록 조회 API") - class LoadMissionsByStamp { - private ResultActions getResultActions(String accessToken, Object tripId, Object stampId) - throws Exception { - return mockMvc.perform( - get(BASE_MISSION_URL, tripId, stampId) - .header( - HttpHeaders.AUTHORIZATION, - TokenFixture.TOKEN_PREFIX + accessToken)); - } - - @Test - @DisplayName("Access Token이 없으면 401 Unauthorized를 반환한다.") - void shouldReturnUnauthorizedWhenAccessTokenIsMissing() throws Exception { - // when - ResultActions resultActions = - getResultActions("", exploreTrip.getId(), exploreStamp.getId()); - - // then - resultActions - .andExpect(status().isUnauthorized()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value(AuthErrorCode.UNAUTHENTICATED.getStatus().value())); - } - - @Test - @DisplayName("PathVariable 여행 ID 타입이 올바르지 않으면 400 Bad Request를 반환한다.") - void shouldReturnBadRequestWhenTripIdTypeMismatch() throws Exception { - // given - String invalidTripId = "abc"; - - // when - ResultActions resultActions = - getResultActions(accessToken, invalidTripId, exploreStamp.getId()); - - // then - resultActions - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value( - CommonErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH - .getStatus() - .value())); - } - - @Test - @DisplayName("PathVariable 스탬프 ID 타입이 올바르지 않으면 400 Bad Request를 반환한다.") - void shouldReturnBadRequestWhenStampIdTypeMismatch() throws Exception { - // given - String invalidStampId = "def"; - - // when - ResultActions resultActions = - getResultActions(accessToken, exploreTrip.getId(), invalidStampId); - - // then - resultActions - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value( - CommonErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH - .getStatus() - .value())); - } - - @Test - @DisplayName("삭제된 여행일 경우 400 Bad Request를 반환한다.") - void shouldReturnBadRequestWhenTripAlreadyDeleted() throws Exception { - // given - courseTrip.updateDeletedAt(); - - // when - ResultActions resultActions = - getResultActions(accessToken, courseTrip.getId(), courseStamp.getId()); - - // then - resultActions - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value(TripErrorCode.TRIP_ALREADY_DELETED.getStatus().value())); - } - - @Test - @DisplayName("삭제된 스탬프일 경우 400 Bad Request를 반환한다.") - void shouldReturnBadRequestWhenStampAlreadyDeleted() throws Exception { - // given - courseStamp.updateDeletedAt(); - - // when - ResultActions resultActions = - getResultActions(accessToken, courseTrip.getId(), courseStamp.getId()); - - // then - resultActions - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value( - StampErrorCode.STAMP_ALREADY_DELETED - .getStatus() - .value())); - } - - @Test - @DisplayName("여행의 소유자가 아니라면 403 Forbidden을 반환한다.") - void shouldReturnForbiddenWhenNotTripOwner() throws Exception { - // when - ResultActions resultActions = - getResultActions(newAccessToken, exploreTrip.getId(), newStamp.getId()); - - // then - resultActions - .andExpect(status().isForbidden()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value(TripErrorCode.NOT_TRIP_OWNER.getStatus().value())); - } - - @Test - @DisplayName("스탬프가 요청한 여행에 속하지 않으면 403 Forbidden을 반환한다.") - void shouldReturnForbiddenWhenStampNotBelongToTrip() throws Exception { - // when - ResultActions resultActions = - getResultActions(accessToken, courseTrip.getId(), exploreStamp.getId()); - - // then - resultActions - .andExpect(status().isForbidden()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value( - StampErrorCode.STAMP_NOT_BELONG_TO_TRIP - .getStatus() - .value())); - } - - @Test - @DisplayName("유효하지 않은 여행 ID가 들어오면 404 Not Found를 반환한다.") - void shouldReturnNotFoundWhenTripIdIsInvalid() throws Exception { - // given - Long invalidTripId = 10000L; - - // when - ResultActions resultActions = - getResultActions(accessToken, invalidTripId, exploreStamp.getId()); - - // when & then - resultActions - .andExpect(status().isNotFound()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value(TripErrorCode.TRIP_NOT_FOUND.getStatus().value())); - } - - @Test - @DisplayName("유효하지 않은 스탬프 ID가 들어오면 404 Not Found를 반환한다.") - void shouldReturnNotFoundWhenStampIdIsInvalid() throws Exception { - // given - Long invalidStampId = 10000L; - - // when - ResultActions resultActions = - getResultActions(accessToken, exploreTrip.getId(), invalidStampId); - - // when & then - resultActions - .andExpect(status().isNotFound()) - .andExpect(jsonPath("$.success").value(false)) - .andExpect( - jsonPath("$.status") - .value(StampErrorCode.STAMP_NOT_FOUND.getStatus().value())); - } - - @Test - @DisplayName("유효한 요청이 들어오면 미션 목록을 조회한다.") - void shouldLoadMissionsByStampWhenRequestIsValid() throws Exception { - // when - ResultActions resultActions = - getResultActions(accessToken, exploreTrip.getId(), exploreStamp.getId()); - - // then - resultActions - .andExpect(status().isOk()) - .andExpect(jsonPath("$.success").value(true)) - .andExpect(jsonPath("$.status").value(HttpStatus.OK.value())); - } - } -} diff --git a/src/test/java/com/ject/studytrip/stamp/application/service/StampCommandServiceTest.java b/src/test/java/com/ject/studytrip/stamp/application/service/StampCommandServiceTest.java index 52413da..395b065 100644 --- a/src/test/java/com/ject/studytrip/stamp/application/service/StampCommandServiceTest.java +++ b/src/test/java/com/ject/studytrip/stamp/application/service/StampCommandServiceTest.java @@ -270,7 +270,7 @@ void shouldThrowExceptionWhenStampDoesNotBelongToTrip() { // when & then assertThatThrownBy(() -> stampCommandService.updateStampOrders(newTrip, request)) .isInstanceOf(CustomException.class) - .hasMessage(StampErrorCode.STAMP_NOT_BELONG_TO_TRIP.getMessage()); + .hasMessage(StampErrorCode.STAMP_NOT_BELONGS_TO_TRIP.getMessage()); } @Test diff --git a/src/test/java/com/ject/studytrip/stamp/application/service/StampQueryServiceTest.java b/src/test/java/com/ject/studytrip/stamp/application/service/StampQueryServiceTest.java index ef6dbf3..90efe60 100644 --- a/src/test/java/com/ject/studytrip/stamp/application/service/StampQueryServiceTest.java +++ b/src/test/java/com/ject/studytrip/stamp/application/service/StampQueryServiceTest.java @@ -77,7 +77,7 @@ void shouldThrowExceptionWhenStampDoesNotBelongToTrip() { stampQueryService.getValidStamp( exploreTrip.getId(), courseStamp1.getId())) .isInstanceOf(CustomException.class) - .hasMessage(StampErrorCode.STAMP_NOT_BELONG_TO_TRIP.getMessage()); + .hasMessage(StampErrorCode.STAMP_NOT_BELONGS_TO_TRIP.getMessage()); } @Test diff --git a/src/test/java/com/ject/studytrip/stamp/presentation/controller/StampControllerIntegrationTest.java b/src/test/java/com/ject/studytrip/stamp/presentation/controller/StampControllerIntegrationTest.java index 04a5559..46244b0 100644 --- a/src/test/java/com/ject/studytrip/stamp/presentation/controller/StampControllerIntegrationTest.java +++ b/src/test/java/com/ject/studytrip/stamp/presentation/controller/StampControllerIntegrationTest.java @@ -432,7 +432,7 @@ void shouldThrowExceptionWhenStampTripMisMatch() throws Exception { .andExpect( jsonPath("$.status") .value( - StampErrorCode.STAMP_NOT_BELONG_TO_TRIP + StampErrorCode.STAMP_NOT_BELONGS_TO_TRIP .getStatus() .value())); } @@ -669,7 +669,7 @@ void shouldThrowExceptionWhenStampTripMisMatch() throws Exception { .andExpect( jsonPath("$.status") .value( - StampErrorCode.STAMP_NOT_BELONG_TO_TRIP + StampErrorCode.STAMP_NOT_BELONGS_TO_TRIP .getStatus() .value())); } @@ -876,7 +876,7 @@ void shouldThrowExceptionWhenStampTripMisMatch() throws Exception { .andExpect( jsonPath("$.status") .value( - StampErrorCode.STAMP_NOT_BELONG_TO_TRIP + StampErrorCode.STAMP_NOT_BELONGS_TO_TRIP .getStatus() .value())); } @@ -1170,7 +1170,7 @@ void shouldThrowExceptionWhenStampTripMisMatch() throws Exception { .andExpect( jsonPath("$.status") .value( - StampErrorCode.STAMP_NOT_BELONG_TO_TRIP + StampErrorCode.STAMP_NOT_BELONGS_TO_TRIP .getStatus() .value())); } @@ -1336,12 +1336,12 @@ void shouldReturnForbiddenWhenStampNotBelongToTrip() throws Exception { .andExpect( jsonPath("$.status") .value( - StampErrorCode.STAMP_NOT_BELONG_TO_TRIP + StampErrorCode.STAMP_NOT_BELONGS_TO_TRIP .getStatus() .value())) .andExpect( jsonPath("$.data.message") - .value(StampErrorCode.STAMP_NOT_BELONG_TO_TRIP.getMessage())); + .value(StampErrorCode.STAMP_NOT_BELONGS_TO_TRIP.getMessage())); } @Test diff --git a/src/test/java/com/ject/studytrip/studylog/application/service/StudyLogDailyMissionCommandServiceTest.java b/src/test/java/com/ject/studytrip/studylog/application/service/StudyLogDailyMissionCommandServiceTest.java deleted file mode 100644 index 05322c5..0000000 --- a/src/test/java/com/ject/studytrip/studylog/application/service/StudyLogDailyMissionCommandServiceTest.java +++ /dev/null @@ -1,228 +0,0 @@ -package com.ject.studytrip.studylog.application.service; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.mockito.ArgumentMatchers.anyList; -import static org.mockito.BDDMockito.given; - -import com.ject.studytrip.BaseUnitTest; -import com.ject.studytrip.member.domain.model.Member; -import com.ject.studytrip.member.fixture.MemberFixture; -import com.ject.studytrip.mission.domain.model.DailyMission; -import com.ject.studytrip.mission.domain.model.Mission; -import com.ject.studytrip.mission.fixture.DailyMissionFixture; -import com.ject.studytrip.mission.fixture.MissionFixture; -import com.ject.studytrip.stamp.domain.model.Stamp; -import com.ject.studytrip.stamp.fixture.StampFixture; -import com.ject.studytrip.studylog.domain.model.StudyLog; -import com.ject.studytrip.studylog.domain.model.StudyLogDailyMission; -import com.ject.studytrip.studylog.domain.repository.StudyLogDailyMissionCommandRepository; -import com.ject.studytrip.studylog.domain.repository.StudyLogDailyMissionRepository; -import com.ject.studytrip.studylog.fixture.StudyLogFixture; -import com.ject.studytrip.trip.domain.model.DailyGoal; -import com.ject.studytrip.trip.domain.model.Trip; -import com.ject.studytrip.trip.domain.model.TripCategory; -import com.ject.studytrip.trip.fixture.DailyGoalFixture; -import com.ject.studytrip.trip.fixture.TripFixture; -import java.util.List; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; - -@DisplayName("StudyLogDailyMissionCommandService 단위 테스트") -class StudyLogDailyMissionCommandServiceTest extends BaseUnitTest { - @InjectMocks private StudyLogDailyMissionCommandService studyLogDailyMissionCommandService; - @Mock private StudyLogDailyMissionRepository studyLogDailyMissionRepository; - @Mock private StudyLogDailyMissionCommandRepository studyLogDailyMissionCommandRepository; - - private Member member; - private Mission mission1; - private Mission mission2; - private DailyGoal dailyGoal; - - @BeforeEach - void setUp() { - member = MemberFixture.createMemberFromKakaoWithId(1L); - Trip trip = TripFixture.createTripWithId(1L, member, TripCategory.COURSE); - Stamp stamp = StampFixture.createStampWithId(1L, trip, 1); - mission1 = MissionFixture.createMissionWithId(1L, stamp); - mission2 = MissionFixture.createMissionWithId(2L, stamp); - dailyGoal = DailyGoalFixture.createDailyGoalWithId(1L, trip); - } - - @Nested - @DisplayName("createStudyLogDailyMissions 메서드는") - class createStudyLogDailyMissions { - - @Test - @DisplayName("학습 로그와 데일리 미션 목록으로 학습 로그 데일리 미션을 생성하여 저장하고 반환한다") - void shouldReturnCreateStudyLogDailyMissions() { - // given - StudyLog studyLog = new StudyLogFixture(member, dailyGoal).createWithId(1L); - DailyMission dailyMission1 = - DailyMissionFixture.createDailyMissionWithId(1L, mission1, dailyGoal); - DailyMission dailyMission2 = - DailyMissionFixture.createDailyMissionWithId(2L, mission2, dailyGoal); - List dailyMissions = List.of(dailyMission1, dailyMission2); - - given(studyLogDailyMissionRepository.saveAll(anyList())) - .willAnswer(invocation -> invocation.getArgument(0)); - - // when - List result = - studyLogDailyMissionCommandService.createStudyLogDailyMissions( - studyLog, dailyMissions); - - // then - assertThat(result.size()).isEqualTo(dailyMissions.size()); - } - } - - @Nested - @DisplayName("hardDeleteStudyLogDailyMissions 메서드는") - class HardDeleteStudyLogDailyMissions { - - @Test - @DisplayName("삭제된 StudyLogDailyMission이 없으면 0을 반환한다.") - void shouldReturnZeroWhenDeletedStudyLogDailyMissionsDoNotExist() { - // given - given(studyLogDailyMissionCommandRepository.deleteAllByDeletedAtIsNotNull()) - .willReturn(0L); - - // when - long result = studyLogDailyMissionCommandService.hardDeleteStudyLogDailyMissions(); - - // then - assertThat(result).isEqualTo(0L); - } - - @Test - @DisplayName("삭제된 StudyLogDailyMission이 있으면 해당 개수를 반환한다.") - void shouldReturnCountWhenDeletedStudyLogDailyMissionsExist() { - // given - given(studyLogDailyMissionCommandRepository.deleteAllByDeletedAtIsNotNull()) - .willReturn(5L); - - // when - long result = studyLogDailyMissionCommandService.hardDeleteStudyLogDailyMissions(); - - // then - assertThat(result).isEqualTo(5L); - } - } - - @Nested - @DisplayName("hardDeleteStudyLogDailyMissionsOwnedByDeletedDailyMission 메서드는") - class HardDeleteStudyLogDailyMissionsOwnedByDeletedDailyMission { - - @Test - @DisplayName("삭제된 데일리 미션이 소유한 StudyLogDailyMission이 없으면 0을 반환한다.") - void shouldReturnZeroWhenStudyLogDailyMissionsOwnedByDeletedDailyMissionDoNotExist() { - // given - given(studyLogDailyMissionCommandRepository.deleteAllByDeletedDailyMissionOwner()) - .willReturn(0L); - - // when - long result = - studyLogDailyMissionCommandService - .hardDeleteStudyLogDailyMissionsOwnedByDeletedDailyMission(); - - // then - assertThat(result).isEqualTo(0L); - } - - @Test - @DisplayName("삭제된 데일리 미션이 소유한 StudyLogDailyMission이 있으면 해당 개수를 반환한다.") - void shouldReturnCountWhenStudyLogDailyMissionsOwnedByDeletedDailyMissionExist() { - // given - given(studyLogDailyMissionCommandRepository.deleteAllByDeletedDailyMissionOwner()) - .willReturn(5L); - - // when - long result = - studyLogDailyMissionCommandService - .hardDeleteStudyLogDailyMissionsOwnedByDeletedDailyMission(); - - // then - assertThat(result).isEqualTo(5L); - } - } - - @Nested - @DisplayName("hardDeleteStudyLogDailyMissionsOwnedByDeletedStudyLog 메서드는") - class HardDeleteStudyLogDailyMissionsOwnedByDeletedStudyLog { - - @Test - @DisplayName("삭제된 학습 로그가 소유한 StudyLogDailyMission이 없으면 0을 반환한다.") - void shouldReturnZeroWhenDailyMissionsOwnedByDeletedStudyLogDoNotExist() { - // given - given(studyLogDailyMissionCommandRepository.deleteAllByDeletedStudyLogOwner()) - .willReturn(0L); - - // when - long result = - studyLogDailyMissionCommandService - .hardDeleteStudyLogDailyMissionsOwnedByDeletedStudyLog(); - - // then - assertThat(result).isEqualTo(0L); - } - - @Test - @DisplayName("삭제된 학습 로그가 소유한 StudyLogDailyMission이 있으면 해당 개수를 반환한다.") - void shouldReturnCountWhenStudyLogDailyMissionsOwnedByDeletedStudyLogExist() { - // given - given(studyLogDailyMissionCommandRepository.deleteAllByDeletedStudyLogOwner()) - .willReturn(5L); - - // when - long result = - studyLogDailyMissionCommandService - .hardDeleteStudyLogDailyMissionsOwnedByDeletedStudyLog(); - - // then - assertThat(result).isEqualTo(5L); - } - } - - @Nested - @DisplayName("hardDeleteStudyLogDailyMissionsByMember 메서드는") - class HardDeleteStudyLogDailyMissionsByMember { - - @Test - @DisplayName("특정 멤버가 소유한 학습 로그 데일리 미션이 없으면 0을 반환한다.") - void shouldReturnZeroWhenStudyLogDailyMissionsOwnedByMemberDoNotExist() { - // given - Long memberId = 1L; - given(studyLogDailyMissionCommandRepository.deleteAllByMemberId(memberId)) - .willReturn(0L); - - // when - long result = - studyLogDailyMissionCommandService.hardDeleteStudyLogDailyMissionsByMember( - memberId); - - // then - assertThat(result).isEqualTo(0L); - } - - @Test - @DisplayName("특정 멤버가 소유한 학습 로그 데일리 미션이 있으면 해당 개수를 반환한다.") - void shouldReturnCountWhenStudyLogDailyMissionsOwnedByMemberExist() { - // given - Long memberId = 1L; - given(studyLogDailyMissionCommandRepository.deleteAllByMemberId(memberId)) - .willReturn(5L); - - // when - long result = - studyLogDailyMissionCommandService.hardDeleteStudyLogDailyMissionsByMember( - memberId); - - // then - assertThat(result).isEqualTo(5L); - } - } -} diff --git a/src/test/java/com/ject/studytrip/studylog/application/service/StudyLogDailyMissionQueryServiceTest.java b/src/test/java/com/ject/studytrip/studylog/application/service/StudyLogDailyMissionQueryServiceTest.java deleted file mode 100644 index e648067..0000000 --- a/src/test/java/com/ject/studytrip/studylog/application/service/StudyLogDailyMissionQueryServiceTest.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.ject.studytrip.studylog.application.service; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.*; - -import com.ject.studytrip.BaseUnitTest; -import com.ject.studytrip.studylog.domain.model.StudyLogDailyMission; -import com.ject.studytrip.studylog.domain.repository.StudyLogDailyMissionQueryRepository; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; - -@DisplayName("StudyLogDailyMissionQueryService 단위 테스트") -class StudyLogDailyMissionQueryServiceTest extends BaseUnitTest { - @InjectMocks private StudyLogDailyMissionQueryService studyLogDailyMissionQueryService; - @Mock private StudyLogDailyMissionQueryRepository studyLogDailyMissionQueryRepository; - - @Nested - @DisplayName("getGroupedStudyLogDailyMissionsByStudyLogIds 메서드는") - class getGroupedStudyLogDailyMissionsByStudyLogIds { - - @Test - @DisplayName("학습 로그 ID 리스트로 그룹화된 StudyLogDailyMission Map을 반환한다") - void shouldReturnGroupedStudyLogDailyMissionMap() { - // given - Long studyLogId1 = 1L; - Long studyLogId2 = 2L; - - StudyLogDailyMission studyLogDailyMission1 = mock(StudyLogDailyMission.class); - StudyLogDailyMission studyLogDailyMission2 = mock(StudyLogDailyMission.class); - StudyLogDailyMission studyLogDailyMission3 = mock(StudyLogDailyMission.class); - - Map> mockResult = new HashMap<>(); - mockResult.put(studyLogId1, List.of(studyLogDailyMission1, studyLogDailyMission2)); - mockResult.put(studyLogId2, List.of(studyLogDailyMission3)); - - List studyLogIds = List.of(studyLogId1, studyLogId2); - - given( - studyLogDailyMissionQueryRepository - .findStudyLogDailyMissionsGroupedByStudyLogId(studyLogIds)) - .willReturn(mockResult); - - // when - Map> result = - studyLogDailyMissionQueryService.getGroupedStudyLogDailyMissionsByStudyLogIds( - studyLogIds); - - // then - assertThat(result).isEqualTo(mockResult); - verify(studyLogDailyMissionQueryRepository, times(1)) - .findStudyLogDailyMissionsGroupedByStudyLogId(studyLogIds); - } - } -} diff --git a/src/test/java/com/ject/studytrip/studylog/fixture/StudyLogDailyMissionFixture.java b/src/test/java/com/ject/studytrip/studylog/fixture/StudyLogDailyMissionFixture.java deleted file mode 100644 index d532d39..0000000 --- a/src/test/java/com/ject/studytrip/studylog/fixture/StudyLogDailyMissionFixture.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.ject.studytrip.studylog.fixture; - -import com.ject.studytrip.mission.domain.model.DailyMission; -import com.ject.studytrip.studylog.domain.factory.StudyLogDailyMissionFactory; -import com.ject.studytrip.studylog.domain.model.StudyLog; -import com.ject.studytrip.studylog.domain.model.StudyLogDailyMission; -import org.springframework.test.util.ReflectionTestUtils; - -public class StudyLogDailyMissionFixture { - public static StudyLogDailyMission createStudyLogDailyMission( - StudyLog studyLog, DailyMission dailyMission) { - return StudyLogDailyMissionFactory.create(studyLog, dailyMission); - } - - public static StudyLogDailyMission createStudyLogDailyMissionWithId( - Long id, StudyLog studyLog, DailyMission dailyMission) { - StudyLogDailyMission studyLogDailyMission = - createStudyLogDailyMission(studyLog, dailyMission); - ReflectionTestUtils.setField(studyLogDailyMission, "id", id); - - return studyLogDailyMission; - } -} diff --git a/src/test/java/com/ject/studytrip/studylog/helper/StudyLogDailyMissionTestHelper.java b/src/test/java/com/ject/studytrip/studylog/helper/StudyLogDailyMissionTestHelper.java deleted file mode 100644 index 45f63c4..0000000 --- a/src/test/java/com/ject/studytrip/studylog/helper/StudyLogDailyMissionTestHelper.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.ject.studytrip.studylog.helper; - -import com.ject.studytrip.mission.domain.model.DailyMission; -import com.ject.studytrip.studylog.domain.model.StudyLog; -import com.ject.studytrip.studylog.domain.model.StudyLogDailyMission; -import com.ject.studytrip.studylog.domain.repository.StudyLogDailyMissionRepository; -import com.ject.studytrip.studylog.fixture.StudyLogDailyMissionFixture; -import java.util.List; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -@Component -public class StudyLogDailyMissionTestHelper { - @Autowired private StudyLogDailyMissionRepository studyLogDailyMissionRepository; - - public List saveStudyLogDailyMissions( - StudyLog studyLog, DailyMission dailyMission) { - StudyLogDailyMission studyLogDailyMission = - StudyLogDailyMissionFixture.createStudyLogDailyMission(studyLog, dailyMission); - return studyLogDailyMissionRepository.saveAll(List.of(studyLogDailyMission)); - } -} diff --git a/src/test/java/com/ject/studytrip/trip/presentation/controller/DailyGoalControllerIntegrationTest.java b/src/test/java/com/ject/studytrip/trip/presentation/controller/DailyGoalControllerIntegrationTest.java index f2a6c68..1bc4bbf 100644 --- a/src/test/java/com/ject/studytrip/trip/presentation/controller/DailyGoalControllerIntegrationTest.java +++ b/src/test/java/com/ject/studytrip/trip/presentation/controller/DailyGoalControllerIntegrationTest.java @@ -332,7 +332,7 @@ void shouldReturnForbiddenWhenMissionsStampsDoesNotBelongToTrip() throws Excepti .andExpect( jsonPath("$.status") .value( - StampErrorCode.STAMP_NOT_BELONG_TO_TRIP + StampErrorCode.STAMP_NOT_BELONGS_TO_TRIP .getStatus() .value())); } @@ -683,7 +683,7 @@ void shouldReturnForbiddenWhenDeleteTargetDailyMissionDoesNotBelongToDailyGoal() jsonPath("$.status") .value( DailyMissionErrorCode - .DAILY_MISSION_NOT_BELONG_TO_DAILY_GOAL + .DAILY_MISSION_NOT_BELONGS_TO_DAILY_GOAL .getStatus() .value())); } @@ -775,7 +775,7 @@ void shouldReturnForbiddenWhenAddMissionsStampsDoesNotBelongToTrip() throws Exce .andExpect( jsonPath("$.status") .value( - StampErrorCode.STAMP_NOT_BELONG_TO_TRIP + StampErrorCode.STAMP_NOT_BELONGS_TO_TRIP .getStatus() .value())); } diff --git a/src/test/kotlin/com/ject/studytrip/mission/application/service/DailyMissionCommandServiceTest.kt b/src/test/kotlin/com/ject/studytrip/mission/application/service/DailyMissionCommandServiceTest.kt new file mode 100644 index 0000000..8c7d79e --- /dev/null +++ b/src/test/kotlin/com/ject/studytrip/mission/application/service/DailyMissionCommandServiceTest.kt @@ -0,0 +1,207 @@ +package com.ject.studytrip.mission.application.service + +import com.ject.studytrip.BaseUnitTest +import com.ject.studytrip.member.domain.model.Member +import com.ject.studytrip.member.fixture.MemberFixture +import com.ject.studytrip.mission.domain.model.DailyMission +import com.ject.studytrip.mission.domain.model.Mission +import com.ject.studytrip.mission.domain.repository.DailyMissionCommandRepository +import com.ject.studytrip.mission.domain.repository.DailyMissionRepository +import com.ject.studytrip.mission.fixture.DailyMissionFixture +import com.ject.studytrip.mission.fixture.MissionFixture +import com.ject.studytrip.stamp.fixture.StampFixture +import com.ject.studytrip.trip.domain.model.DailyGoal +import com.ject.studytrip.trip.domain.model.TripCategory +import com.ject.studytrip.trip.fixture.DailyGoalFixture +import com.ject.studytrip.trip.fixture.TripFixture +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.mockito.ArgumentMatchers.anyList +import org.mockito.BDDMockito.given +import org.mockito.InjectMocks +import org.mockito.Mock + +@DisplayName("DailyMissionCommandService 단위 테스트") +class DailyMissionCommandServiceTest : BaseUnitTest() { + @InjectMocks + private lateinit var dailyMissionCommandService: DailyMissionCommandService + + @Mock + private lateinit var dailyMissionRepository: DailyMissionRepository + + @Mock + private lateinit var dailyMissionCommandRepository: DailyMissionCommandRepository + + private lateinit var member: Member + private lateinit var mission: Mission + private lateinit var dailyGoal: DailyGoal + private lateinit var dailyMission: DailyMission + + @BeforeEach + fun setUp() { + member = MemberFixture.createMemberFromKakaoWithId(1L) + val trip = TripFixture.createTripWithId(1L, member, TripCategory.COURSE) + val stamp = StampFixture.createStampWithId(1L, trip, 1) + mission = MissionFixture(stamp).createWithId(1L) + dailyGoal = DailyGoalFixture.createDailyGoalWithId(1L, trip) + dailyMission = DailyMissionFixture(mission, dailyGoal).createWithId(1L) + } + + @Nested + @DisplayName("createDailyMissions 메서드는") + inner class CreateDailyMissions { + @Test + @DisplayName("데일리 미션 목록을 생성하고 반환한다.") + fun shouldCreateAndReturnDailyMissions() { + // given + val missions = listOf(mission) + given(dailyMissionRepository.saveAll(anyList())).willReturn(listOf(dailyMission)) + + // when + val result = dailyMissionCommandService.createDailyMissions(dailyGoal, missions) + + // then + assertThat(result).hasSize(missions.size) + assertThat(result).contains(dailyMission) + } + } + + @Nested + @DisplayName("deleteDailyMission 메서드는") + inner class DeleteDailyMission { + @Test + @DisplayName("데일리 미션이 삭제될 때 deletedAt 필드를 현재 시간으로 업데이트한다. (소프트 삭제)") + fun shouldUpdateDeletedAtWhenDailyMissionIsDeleted() { + // when + dailyMissionCommandService.deleteDailyMission(dailyMission) + + // then + assertThat(dailyMission.deletedAt).isNotNull + } + } + + @Nested + @DisplayName("hardDeleteDailyMissions 메서드는") + inner class HardDeleteDailyMissions { + @Test + @DisplayName("삭제된 데일리 미션이 하나라도 존재하지 않으면 0을 반환한다.") + fun shouldReturnZeroWhenDeletedDailyMissionsDoNotExist() { + // given + given(dailyMissionCommandRepository.deleteAllByDeletedAtIsNotNull()).willReturn(0L) + + // when + val result = dailyMissionCommandService.hardDeleteDailyMissions() + + // then + assertThat(result).isEqualTo(0L) + } + + @Test + @DisplayName("삭제된 데일리 미션이 하나라도 존재하면 해당 개수를 반환한다.") + fun shouldReturnCountWhenDeletedDailyMissionsExist() { + // given + given(dailyMissionCommandRepository.deleteAllByDeletedAtIsNotNull()).willReturn(5L) + + // when + val result = dailyMissionCommandService.hardDeleteDailyMissions() + + // then + assertThat(result).isEqualTo(5L) + } + } + + @Nested + @DisplayName("hardDeleteDailyMissionsOwnedByDeletedMission 메서드는") + inner class HardDeleteDailyMissionsOwnedByDeletedMission { + @Test + @DisplayName("삭제된 미션이 소유한 데일리 미션이 하나라도 존재하지 않으면 0을 반환한다.") + fun shouldReturnZeroWhenDailyMissionsOwnedByDeletedMissionDoNotExist() { + // given + given(dailyMissionCommandRepository.deleteAllByDeletedMissionOwner()).willReturn(0L) + + // when + val result = dailyMissionCommandService.hardDeleteDailyMissionsOwnedByDeletedMission() + + // then + assertThat(result).isEqualTo(0L) + } + + @Test + @DisplayName("삭제된 미션이 소유한 데일리 미션이 하나라도 존재하면 해당 개수를 반환한다.") + fun shouldReturnCountWhenDailyMissionsOwnedByDeletedMissionExist() { + // given + given(dailyMissionCommandRepository.deleteAllByDeletedMissionOwner()).willReturn(5L) + + // when + val result = dailyMissionCommandService.hardDeleteDailyMissionsOwnedByDeletedMission() + + // then + assertThat(result).isEqualTo(5L) + } + } + + @Nested + @DisplayName("hardDeleteDailyMissionsOwnedByDeletedDailyGoal 메서드는") + inner class HardDeleteDailyMissionsOwnedByDeletedDailyGoal { + @Test + @DisplayName("삭제된 데일리 목표가 소유한 데일리 미션이 하나라도 존재하지 않으면 0을 반환한다.") + fun shouldReturnZeroWhenDailyMissionsOwnedByDeletedDailyGoalDoNotExist() { + // given + given(dailyMissionCommandRepository.deleteAllByDeletedDailyGoalOwner()).willReturn(0L) + + // when + val result = dailyMissionCommandService.hardDeleteDailyMissionsOwnedByDeletedDailyGoal() + + // then + assertThat(result).isEqualTo(0L) + } + + @Test + @DisplayName("삭제된 데일리 목표가 소유한 데일리 미션이 하나라도 존재하면 해당 개수를 반환한다.") + fun shouldReturnCountWhenDailyMissionsOwnedByDeletedDailyGoalExist() { + // given + given(dailyMissionCommandRepository.deleteAllByDeletedDailyGoalOwner()).willReturn(5L) + + // when + val result = dailyMissionCommandService.hardDeleteDailyMissionsOwnedByDeletedDailyGoal() + + // then + assertThat(result).isEqualTo(5L) + } + } + + @Nested + @DisplayName("hardDeleteDailyMissionsOwnedByMember 메서드는") + inner class HardDeleteDailyMissionsOwnedByMember { + @Test + @DisplayName("특정 멤버가 소유한 데일리 미션이 하나라도 존재하지 않으면 0을 반환한다.") + fun shouldReturnZeroWhenDailyMissionsOwnedByMemberDoNotExist() { + // given + val memberId = member.id + given(dailyMissionCommandRepository.deleteAllByMemberId(memberId)).willReturn(0L) + + // when + val result = dailyMissionCommandService.hardDeleteDailyMissionsOwnedByMember(memberId) + + // then + assertThat(result).isEqualTo(0L) + } + + @Test + @DisplayName("특정 멤버가 소유한 데일리 미션이 하나라도 존재하면 해당 개수를 반환한다.") + fun shouldReturnCountWhenDailyMissionsOwnedByMemberExist() { + // given + val memberId = member.id + given(dailyMissionCommandRepository.deleteAllByMemberId(memberId)).willReturn(5L) + + // when + val result = dailyMissionCommandService.hardDeleteDailyMissionsOwnedByMember(memberId) + + // then + assertThat(result).isEqualTo(5L) + } + } +} diff --git a/src/test/kotlin/com/ject/studytrip/mission/application/service/DailyMissionQueryServiceTest.kt b/src/test/kotlin/com/ject/studytrip/mission/application/service/DailyMissionQueryServiceTest.kt new file mode 100644 index 0000000..91c52f5 --- /dev/null +++ b/src/test/kotlin/com/ject/studytrip/mission/application/service/DailyMissionQueryServiceTest.kt @@ -0,0 +1,218 @@ +package com.ject.studytrip.mission.application.service + +import com.ject.studytrip.BaseUnitTest +import com.ject.studytrip.global.exception.CustomException +import com.ject.studytrip.member.fixture.MemberFixture +import com.ject.studytrip.mission.domain.error.DailyMissionErrorCode +import com.ject.studytrip.mission.domain.model.DailyMission +import com.ject.studytrip.mission.domain.repository.DailyMissionQueryRepository +import com.ject.studytrip.mission.domain.repository.DailyMissionRepository +import com.ject.studytrip.mission.fixture.DailyMissionFixture +import com.ject.studytrip.mission.fixture.MissionFixture +import com.ject.studytrip.stamp.fixture.StampFixture +import com.ject.studytrip.trip.domain.model.DailyGoal +import com.ject.studytrip.trip.domain.model.Trip +import com.ject.studytrip.trip.domain.model.TripCategory +import com.ject.studytrip.trip.fixture.DailyGoalFixture +import com.ject.studytrip.trip.fixture.TripFixture +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.mockito.BDDMockito.given +import org.mockito.InjectMocks +import org.mockito.Mock + +@DisplayName("DailyMissionQueryService 단위 테스트") +class DailyMissionQueryServiceTest : BaseUnitTest() { + @InjectMocks + private lateinit var dailyMissionQueryService: DailyMissionQueryService + + @Mock + private lateinit var dailyMissionRepository: DailyMissionRepository + + @Mock + private lateinit var dailyMissionQueryRepository: DailyMissionQueryRepository + + private lateinit var trip: Trip + private lateinit var dailyGoal: DailyGoal + private lateinit var dailyMission: DailyMission + + @BeforeEach + fun setUp() { + val member = MemberFixture.createMemberFromKakaoWithId(1L) + trip = TripFixture.createTripWithId(1L, member, TripCategory.COURSE) + val stamp = StampFixture.createStampWithId(1L, trip, 1) + val mission = MissionFixture(stamp).createWithId(1L) + dailyGoal = DailyGoalFixture.createDailyGoalWithId(1L, trip) + dailyMission = DailyMissionFixture(mission, dailyGoal).createWithId(1L) + } + + @Nested + @DisplayName("getValidDailyMissionsByIds 메서드는") + inner class GetValidDailyMissionsByIds { + @Test + @DisplayName("요청한 데일리 미션 ID 개수와 조회된 데일리 미션 개수가 일치하지 않으면 예외가 발생한다.") + fun shouldThrowExceptionWhenSomeDailyMissionsDoNotExist() { + // given + val dailyMissionIds = listOf(dailyMission.id, 1000L) + given(dailyMissionRepository.findAllByIdIn(dailyMissionIds)).willReturn(listOf(dailyMission)) + + // when + val exception = + assertThrows { dailyMissionQueryService.getValidDailyMissionsByIds(dailyGoal.id, dailyMissionIds) } + + // then + assertThat(exception.message).isEqualTo(DailyMissionErrorCode.DAILY_MISSION_NOT_FOUND.message) + } + + @Test + @DisplayName("특정 데일리 목표에 속하지 않은 데일리 미션이 하나라도 존재하면 예외가 발생한다.") + fun shouldThrowExceptionWhenDailyMissionsNotBelongToDailyGoal() { + // given + val newDailyGoal = DailyGoalFixture.createDailyGoalWithId(2L, trip) + val dailyMissionIds = listOf(dailyMission.id) + given(dailyMissionRepository.findAllByIdIn(dailyMissionIds)).willReturn(listOf(dailyMission)) + + // when + val exception = + assertThrows { dailyMissionQueryService.getValidDailyMissionsByIds(newDailyGoal.id, dailyMissionIds) } + + // then + assertThat(exception.message).isEqualTo(DailyMissionErrorCode.DAILY_MISSION_NOT_BELONGS_TO_DAILY_GOAL.message) + } + + @Test + @DisplayName("데일리 미션이 이미 삭제되었다면 예외가 발생한다.") + fun shouldThrowExceptionWhenDailyMissionAlreadyDeleted() { + // given + val dailyMissionIds = listOf(dailyMission.id) + dailyMission.updateDeletedAt() + given(dailyMissionRepository.findAllByIdIn(dailyMissionIds)).willReturn(listOf(dailyMission)) + + // when + val exception = + assertThrows { dailyMissionQueryService.getValidDailyMissionsByIds(dailyGoal.id, dailyMissionIds) } + + // then + assertThat(exception.message).isEqualTo(DailyMissionErrorCode.DAILY_MISSION_ALREADY_DELETED.message) + } + + @Test + @DisplayName("데일리 미션 ID 목록과 일치하는 데일리 미션 목록을 조회하고 반환한다.") + fun shouldReturnDailyMissionsByIds() { + // given + val dailyMissionIds = listOf(dailyMission.id) + given(dailyMissionRepository.findAllByIdIn(dailyMissionIds)).willReturn(listOf(dailyMission)) + + // when + val result = dailyMissionQueryService.getValidDailyMissionsByIds(dailyGoal.id, dailyMissionIds) + + // then + assertThat(result).hasSize(1) + assertThat(result).contains(dailyMission) + } + } + + @Nested + @DisplayName("getValidDailyMissionsWithMissionAndStampByIds 메서드는") + inner class GetValidDailyMissionsWithMissionAndStampByIds { + @Test + @DisplayName("요청한 데일리 미션 ID 개수와 조회된 데일리 미션 개수가 일치하지 않으면 예외가 발생한다.") + fun shouldThrowExceptionWhenSomeDailyMissionsDoNotExist() { + // given + val dailyMissionIds = listOf(dailyMission.id, 1000L) + given(dailyMissionQueryRepository.findAllWithMissionAndStampByIds(dailyMissionIds)).willReturn(listOf(dailyMission)) + + // when + val exception = + assertThrows { + dailyMissionQueryService.getValidDailyMissionsWithMissionAndStampByIds( + dailyGoal.id, + dailyMissionIds, + ) + } + + // then + assertThat(exception.message).isEqualTo(DailyMissionErrorCode.DAILY_MISSION_NOT_FOUND.message) + } + + @Test + @DisplayName("특정 데일리 목표에 속하지 않은 데일리 미션이 하나라도 존재하면 예외가 발생한다.") + fun shouldThrowExceptionWhenDailyMissionsNotBelongToDailyGoal() { + // given + val newDailyGoal = DailyGoalFixture.createDailyGoalWithId(2L, trip) + val dailyMissionIds = listOf(dailyMission.id) + given(dailyMissionQueryRepository.findAllWithMissionAndStampByIds(dailyMissionIds)).willReturn(listOf(dailyMission)) + + // when + val exception = + assertThrows { + dailyMissionQueryService.getValidDailyMissionsWithMissionAndStampByIds( + newDailyGoal.id, + dailyMissionIds, + ) + } + + // then + assertThat(exception.message).isEqualTo(DailyMissionErrorCode.DAILY_MISSION_NOT_BELONGS_TO_DAILY_GOAL.message) + } + + @Test + @DisplayName("데일리 미션이 이미 삭제되었다면 예외가 발생한다.") + fun shouldThrowExceptionWhenDailyMissionAlreadyDeleted() { + // given + val dailyMissionIds = listOf(dailyMission.id) + dailyMission.updateDeletedAt() + given(dailyMissionQueryRepository.findAllWithMissionAndStampByIds(dailyMissionIds)).willReturn(listOf(dailyMission)) + + // when + val exception = + assertThrows { + dailyMissionQueryService.getValidDailyMissionsWithMissionAndStampByIds( + dailyGoal.id, + dailyMissionIds, + ) + } + + // then + assertThat(exception.message).isEqualTo(DailyMissionErrorCode.DAILY_MISSION_ALREADY_DELETED.message) + } + + @Test + @DisplayName("데일리 미션 ID 목록과 일치하는 데일리 미션 목록을 미션과 스탬프와 함께 조회하고 반환한다.") + fun shouldReturnDailyMissionsWithMissionAndStampByIds() { + // given + val dailyMissionIds = listOf(dailyMission.id) + given(dailyMissionQueryRepository.findAllWithMissionAndStampByIds(dailyMissionIds)).willReturn(listOf(dailyMission)) + + // when + val result = dailyMissionQueryService.getValidDailyMissionsWithMissionAndStampByIds(dailyGoal.id, dailyMissionIds) + + // then + assertThat(result).hasSize(1) + assertThat(result).contains(dailyMission) + } + } + + @Nested + @DisplayName("getDailyMissionsByDailyGoalId 메서드는") + inner class GetDailyMissionsByDailyGoalId { + @Test + @DisplayName("데일리 목표 ID로 데일리 미션 목록을 조회하고 반환한다.") + fun shouldReturnDailyMissionsByDailyGoalId() { + // given + val dailyGoalId = dailyGoal.id + given(dailyMissionQueryRepository.findAllByDailyGoalIdFetchJoinMission(dailyGoalId)).willReturn(listOf(dailyMission)) + + // when + val result = dailyMissionQueryService.getDailyMissionsByDailyGoalId(dailyGoalId) + + // then + assertThat(result).hasSize(1) + assertThat(result).contains(dailyMission) + } + } +} diff --git a/src/test/kotlin/com/ject/studytrip/mission/application/service/MissionCommandServiceTest.kt b/src/test/kotlin/com/ject/studytrip/mission/application/service/MissionCommandServiceTest.kt new file mode 100644 index 0000000..7bce540 --- /dev/null +++ b/src/test/kotlin/com/ject/studytrip/mission/application/service/MissionCommandServiceTest.kt @@ -0,0 +1,321 @@ +package com.ject.studytrip.mission.application.service + +import com.ject.studytrip.BaseUnitTest +import com.ject.studytrip.global.exception.CustomException +import com.ject.studytrip.member.fixture.MemberFixture +import com.ject.studytrip.mission.domain.error.MissionErrorCode +import com.ject.studytrip.mission.domain.model.Mission +import com.ject.studytrip.mission.domain.repository.MissionCommandRepository +import com.ject.studytrip.mission.domain.repository.MissionRepository +import com.ject.studytrip.mission.fixture.CreateMissionRequestFixture +import com.ject.studytrip.mission.fixture.MissionFixture +import com.ject.studytrip.mission.fixture.UpdateMissionRequestFixture +import com.ject.studytrip.stamp.domain.model.Stamp +import com.ject.studytrip.stamp.fixture.StampFixture +import com.ject.studytrip.trip.domain.model.TripCategory +import com.ject.studytrip.trip.fixture.TripFixture +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertDoesNotThrow +import org.junit.jupiter.api.assertThrows +import org.mockito.BDDMockito.given +import org.mockito.InjectMocks +import org.mockito.Mock +import org.mockito.kotlin.any + +@DisplayName("MissionCommandService 단위 테스트") +class MissionCommandServiceTest : BaseUnitTest() { + @InjectMocks + private lateinit var missionCommandService: MissionCommandService + + @Mock + private lateinit var missionRepository: MissionRepository + + @Mock + private lateinit var missionCommandRepository: MissionCommandRepository + + private lateinit var courseStamp: Stamp + private lateinit var exploreStamp: Stamp + private lateinit var courseMission: Mission + private lateinit var exploreMission1: Mission + private lateinit var exploreMission2: Mission + + companion object { + private const val NEW_MISSION_NAME = "새오운 미션 이름" + } + + @BeforeEach + fun setUp() { + val member = MemberFixture.createMemberFromKakaoWithId(1L) + val courseTrip = TripFixture.createTripWithId(1L, member, TripCategory.COURSE) + val exploreTrip = TripFixture.createTripWithId(2L, member, TripCategory.EXPLORE) + courseStamp = StampFixture.createStampWithId(1L, courseTrip, 1) + exploreStamp = StampFixture.createStampWithId(2L, exploreTrip, 0) + courseMission = MissionFixture(courseStamp).createWithId(1L) + exploreMission1 = MissionFixture(exploreStamp).createWithId(2L) + exploreMission2 = MissionFixture(exploreStamp).createWithId(3L) + } + + @Nested + @DisplayName("createMission 메서드는") + inner class CreateMission { + private val fixture = CreateMissionRequestFixture() + + @Test + @DisplayName("코스형 스탬프에 대한 미션을 생성하고 반환한다.") + fun shouldCreateAndReturnMissionForCourseStamp() { + // given + val request = fixture.build() + given(missionRepository.save(any())).willReturn(courseMission) + + // when + val result = missionCommandService.createMission(courseStamp, request) + + // then + assertThat(result).isEqualTo(courseMission) + assertThat(result.stamp).isEqualTo(courseStamp) + } + + @Test + @DisplayName("탐험형 스템프에 대한 미션을 생성하고 반환한다.") + fun shouldCreateAndReturnMissionForExploreStamp() { + // given + val request = fixture.build() + given(missionRepository.save(any())).willReturn(exploreMission1) + + // when + val result = missionCommandService.createMission(exploreStamp, request) + + // then + assertThat(result).isEqualTo(exploreMission1) + assertThat(result.stamp).isEqualTo(exploreStamp) + } + } + + @Nested + @DisplayName("updateMissionNameIfPresent 메서드는") + inner class UpdateMissionNameIfPresent { + private val fixture = UpdateMissionRequestFixture() + + @Test + @DisplayName("특정 미션의 이름을 수정한다.") + fun shouldUpdateMissionWhenNameIsPresent() { + // given + val existingName = courseMission.name + val request = fixture.withName(NEW_MISSION_NAME).build() + + // when + missionCommandService.updateMissionNameIfPresent(courseMission, request) + + // then + assertThat(courseMission.name).isEqualTo(NEW_MISSION_NAME) + assertThat(courseMission.name).isNotEqualTo(existingName) + } + } + + @Nested + @DisplayName("deleteMission 메서드는") + inner class DeleteMission { + @Test + @DisplayName("미션이 삭제될 때 deletedAt 필드를 현재 시간으로 업데이트한다. (소프트 삭제)") + fun shouldUpdateDeletedAtWhenMissionIsDeleted() { + // when + missionCommandService.deleteMission(courseMission) + + // then + assertThat(courseMission.deletedAt).isNotNull + } + } + + @Nested + @DisplayName("completeMission 메서드는") + inner class CompleteMission { + @Test + @DisplayName("미션이 이미 삭제되었다면 예외가 발생한다.") + fun shouldThrowExceptionWhenMissionAlreadyDeleted() { + // given + exploreMission1.updateDeletedAt() + + // when + val exception = assertThrows { missionCommandService.completeMission(exploreMission1) } + + // then + assertThat(exception.message).isEqualTo(MissionErrorCode.MISSION_ALREADY_DELETED.message) + } + + @Test + @DisplayName("미션이 이미 완료되었다면 예외가 발생한다.") + fun shouldThrowExceptionWhenMissionAlreadyCompleted() { + // given + exploreMission1.updateCompleted() + + // when + val exception = assertThrows { missionCommandService.completeMission(exploreMission1) } + + // then + assertThat(exception.message).isEqualTo(MissionErrorCode.MISSION_ALREADY_COMPLETED.message) + } + + @Test + @DisplayName("미션이 완료될 때 completed 필드를 true로 업데이트한다.") + fun shouldUpdateCompletedWhenMissionIsCompleted() { + // when + missionCommandService.completeMission(exploreMission1) + + // then + assertThat(exploreMission1.isCompleted).isTrue() + } + } + + @Nested + @DisplayName("validateMissionsBelongsToStamp 메서드는") + inner class ValidateMissionsBelongsToStamp { + @Test + @DisplayName("특정 스탬프에 속하지 않은 미션이 하나라도 존재하면 예외가 발생한다.") + fun shouldThrowExceptionWhenMissionsNotBelongToStamp() { + // given + val missions = listOf(courseMission, exploreMission2) + + // when + val exception = + assertThrows { missionCommandService.validateMissionsBelongsToStamp(exploreStamp.id, missions) } + + // then + assertThat(exception.message).isEqualTo(MissionErrorCode.MISSION_NOT_BELONGS_TO_STAMP.message) + } + + @Test + @DisplayName("모든 미션이 특정 스탬프에 속한다면 예외가 발생하지 않는다.") + fun shouldPassWhenMissionsBelongsToStamp() { + // given + val missions = listOf(exploreMission1, exploreMission2) + + // when & then + assertDoesNotThrow { missionCommandService.validateMissionsBelongsToStamp(exploreStamp.id, missions) } + } + } + + @Nested + @DisplayName("validateAllMissionsCompletedByStampId 메서드는") + inner class ValidateAllMissionsCompletedByStampId { + @Test + @DisplayName("특정 스탬프의 어떤 미션이 완료되지 않았다면 예외가 발생한다.") + fun shouldThrowExceptionWhenAnyMissionIsNotCompleted() { + // given + val stampId = courseStamp.id + given(missionCommandRepository.existsByStampIdAndCompletedIsFalseAndDeletedAtIsNull(stampId)).willReturn(true) + + // when + val exception = assertThrows { missionCommandService.validateAllMissionsCompletedByStampId(stampId) } + + // then + assertThat(exception.message).isEqualTo(MissionErrorCode.ALL_MISSIONS_NOT_COMPLETED.message) + } + + @Test + @DisplayName("특정 스탬프의 모든 미션이 완료되었다면 예외가 발생하지 않는다.") + fun shouldPassWhenAllMissionsAreCompleted() { + // given + val stampId = courseStamp.id + given(missionCommandRepository.existsByStampIdAndCompletedIsFalseAndDeletedAtIsNull(stampId)).willReturn(false) + + // when & then + assertDoesNotThrow { missionCommandService.validateAllMissionsCompletedByStampId(stampId) } + } + } + + @Nested + @DisplayName("hardDeleteMissions 메서드는") + inner class HardDeleteMissions { + @Test + @DisplayName("삭제된 미션이 하나라도 존재하지 않으면 0을 반환한다.") + fun shouldReturnZeroWhenDeletedMissionsDoNotExist() { + // given + given(missionCommandRepository.deleteAllByDeletedAtIsNotNull()).willReturn(0L) + + // when + val result = missionCommandService.hardDeleteMissions() + + // then + assertThat(result).isEqualTo(0L) + } + + @Test + @DisplayName("삭제된 미션이 하나라도 존재하면 해당 개수를 반환한다.") + fun shouldReturnCountWhenDeletedMissionsExist() { + // given + given(missionCommandRepository.deleteAllByDeletedAtIsNotNull()).willReturn(5L) + + // when + val result = missionCommandService.hardDeleteMissions() + + // then + assertThat(result).isEqualTo(5L) + } + } + + @Nested + @DisplayName("hardDeleteMissionsOwnedByDeletedStamp 메서드는") + inner class HardDeleteMissionsOwnedByDeletedStamp { + @Test + @DisplayName("삭제된 스탬프가 소유한 미션이 하나라도 존재하지 않으면 0을 반환한다.") + fun shouldReturnZeroWhenMissionsOwnedByDeletedStampDoNotExist() { + // given + given(missionCommandRepository.deleteAllByDeletedStampOwner()).willReturn(0L) + + // when + val result = missionCommandService.hardDeleteMissionsOwnedByDeletedStamp() + + // then + assertThat(result).isEqualTo(0L) + } + + @Test + @DisplayName("삭제된 스탬프가 소유한 미션이 하나라도 존재하면 해당 개수를 반환한다.") + fun shouldReturnCountWhenMissionsOwnedByDeletedStampExist() { + // given + given(missionCommandRepository.deleteAllByDeletedStampOwner()).willReturn(5L) + + // when + val result = missionCommandService.hardDeleteMissionsOwnedByDeletedStamp() + + // then + assertThat(result).isEqualTo(5L) + } + } + + @Nested + @DisplayName("hardDeleteMissionsOwnedByMember 메서드는") + inner class HardDeleteMissionsOwnedByMember { + @Test + @DisplayName("특정 멤버가 소유한 미션이 하나라도 존재하지 않으면 0을 반환한다.") + fun shouldReturnZeroWhenMissionsOwnedByMemberDoNotExist() { + // given + val memberId = -1L + given(missionCommandRepository.deleteAllByMemberId(memberId)).willReturn(0L) + + // when + val result = missionCommandService.hardDeleteMissionsOwnedByMember(memberId) + + // then + assertThat(result).isEqualTo(0L) + } + + @Test + @DisplayName("특정 멤버가 소유한 미션이 하나라도 존재하면 해당 개수를 반환한다.") + fun shouldReturnCountWhenMissionsOwnedByMemberExist() { + // given + val memberId = -1L + given(missionCommandRepository.deleteAllByMemberId(memberId)).willReturn(5L) + + // when + val result = missionCommandService.hardDeleteMissionsOwnedByMember(memberId) + + // then + assertThat(result).isEqualTo(5L) + } + } +} diff --git a/src/test/kotlin/com/ject/studytrip/mission/application/service/MissionQueryServiceTest.kt b/src/test/kotlin/com/ject/studytrip/mission/application/service/MissionQueryServiceTest.kt new file mode 100644 index 0000000..5c88bdd --- /dev/null +++ b/src/test/kotlin/com/ject/studytrip/mission/application/service/MissionQueryServiceTest.kt @@ -0,0 +1,214 @@ +package com.ject.studytrip.mission.application.service + +import com.ject.studytrip.BaseUnitTest +import com.ject.studytrip.global.exception.CustomException +import com.ject.studytrip.member.fixture.MemberFixture +import com.ject.studytrip.mission.domain.error.MissionErrorCode +import com.ject.studytrip.mission.domain.model.Mission +import com.ject.studytrip.mission.domain.repository.MissionQueryRepository +import com.ject.studytrip.mission.domain.repository.MissionRepository +import com.ject.studytrip.mission.fixture.MissionFixture +import com.ject.studytrip.stamp.domain.model.Stamp +import com.ject.studytrip.stamp.fixture.StampFixture +import com.ject.studytrip.trip.domain.model.TripCategory +import com.ject.studytrip.trip.fixture.TripFixture +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.mockito.BDDMockito.given +import org.mockito.InjectMocks +import org.mockito.Mock +import java.util.Optional + +@DisplayName("MissionQueryService 단위 테스트") +class MissionQueryServiceTest : BaseUnitTest() { + @InjectMocks + private lateinit var missionQueryService: MissionQueryService + + @Mock + private lateinit var missionRepository: MissionRepository + + @Mock + private lateinit var missionQueryRepository: MissionQueryRepository + + private lateinit var courseStamp: Stamp + private lateinit var exploreStamp: Stamp + private lateinit var courseMission: Mission + private lateinit var exploreMission1: Mission + private lateinit var exploreMission2: Mission + + @BeforeEach + fun setUp() { + val member = MemberFixture.createMemberFromKakao() + val courseTrip = TripFixture.createTripWithId(1L, member, TripCategory.COURSE) + val exploreTrip = TripFixture.createTripWithId(2L, member, TripCategory.EXPLORE) + courseStamp = StampFixture.createStampWithId(1L, courseTrip, 1) + exploreStamp = StampFixture.createStampWithId(2L, exploreTrip, 0) + courseMission = MissionFixture(courseStamp).createWithId(1L) + exploreMission1 = MissionFixture(exploreStamp).createWithId(2L) + exploreMission2 = MissionFixture(exploreStamp).createWithId(3L) + } + + @Nested + @DisplayName("getValidMission 메서드는") + inner class GetValidMission { + @Test + @DisplayName("미션이 존재하지 않으면 예외가 발생한다.") + fun shouldThrowExceptionWhenMissionDoesNotExist() { + // given + val missionId = -1L + given(missionRepository.findById(missionId)).willReturn(Optional.empty()) + + // when + val exception = assertThrows { missionQueryService.getValidMission(courseStamp.id, missionId) } + + // then + assertThat(exception.message).isEqualTo(MissionErrorCode.MISSION_NOT_FOUND.message) + } + + @Test + @DisplayName("특정 미션이 다른 스템프에 속한다면 예외가 발생한다.") + fun shouldThrowExceptionWhenMissionNotBelongToStamp() { + // given + val missionId = exploreMission1.id + given(missionRepository.findById(missionId)).willReturn(Optional.of(exploreMission1)) + + // when + val exception = assertThrows { missionQueryService.getValidMission(courseStamp.id, missionId) } + + // then + assertThat(exception.message).isEqualTo(MissionErrorCode.MISSION_NOT_BELONGS_TO_STAMP.message) + } + + @Test + @DisplayName("미션이 이미 삭제되었다면 예외가 발생한다.") + fun shouldThrowExceptionWhenMissionAlreadyDeleted() { + // given + val missionId = exploreMission1.id + exploreMission1.updateDeletedAt() + given(missionRepository.findById(missionId)).willReturn(Optional.of(exploreMission1)) + + // when + val exception = assertThrows { missionQueryService.getValidMission(exploreStamp.id, missionId) } + + // then + assertThat(exception.message).isEqualTo(MissionErrorCode.MISSION_ALREADY_DELETED.message) + } + + @Test + @DisplayName("미션이 이미 완료되었다면 예외가 발생한다.") + fun shouldThrowExceptionWhenMissionAlreadyCompleted() { + // given + val missionId = exploreMission1.id + exploreMission1.updateCompleted() + given(missionRepository.findById(missionId)).willReturn(Optional.of(exploreMission1)) + + // when + val exception = assertThrows { missionQueryService.getValidMission(exploreStamp.id, missionId) } + + // then + assertThat(exception.message).isEqualTo(MissionErrorCode.MISSION_ALREADY_COMPLETED.message) + } + + @Test + @DisplayName("특정 스탬프에 속한 미션이 존재하면 미션을 조회하고 반환한다.") + fun shouldReturnMissionWhenMissionBelongsToStamp() { + // given + val missionId = exploreMission1.id + given(missionRepository.findById(missionId)).willReturn(Optional.of(exploreMission1)) + + // when + val result = missionQueryService.getValidMission(exploreStamp.id, missionId) + + // then + assertThat(result).isEqualTo(exploreMission1) + } + } + + @Nested + @DisplayName("getMissionsByStampId 메서드는") + inner class GetMissionsByStampId { + @Test + @DisplayName("특정 스탬프에 속한 미션 목록을 최신순으로 정렬하여 반환한다.") + fun shouldReturnMissionsByStampIdSortedByLatest() { + // given + val stampId = exploreStamp.id + given( + missionRepository.findAllByStampIdAndDeletedAtIsNullOrderByCreatedAt(stampId), + ).willReturn(listOf(exploreMission2, exploreMission1)) + + // when + val result = missionQueryService.getMissionsByStampId(stampId) + + // then + assertThat(result).hasSize(2) + assertThat(result).containsExactly(exploreMission2, exploreMission1) + } + } + + @Nested + @DisplayName("getValidMissionsByIds 메서드는") + inner class GetValidMissionsByIds { + @Test + @DisplayName("요청한 미션 ID 개수와 조회된 미션 개수가 일치하지 않으면 예외가 발생한다.") + fun shouldThrowExceptionWhenSomeMissionsDoNotExist() { + // given + val missionIds = listOf(exploreMission1.id, exploreMission2.id, 1000L) + given(missionQueryRepository.findAllByIdsInFetchJoinStamp(missionIds)).willReturn(listOf(exploreMission1, exploreMission2)) + + // when + val exception = assertThrows { missionQueryService.getValidMissionsByIds(missionIds) } + + // then + assertThat(exception.message).isEqualTo(MissionErrorCode.MISSION_NOT_FOUND.message) + } + + @Test + @DisplayName("미션이 이미 삭제되었다면 예외가 발생한다.") + fun shouldThrowExceptionWhenMissionAlreadyDeleted() { + // given + val missionIds = listOf(exploreMission1.id, exploreMission2.id) + exploreMission1.updateDeletedAt() + given(missionQueryRepository.findAllByIdsInFetchJoinStamp(missionIds)).willReturn(listOf(exploreMission1, exploreMission2)) + + // when + val exception = assertThrows { missionQueryService.getValidMissionsByIds(missionIds) } + + // then + assertThat(exception.message).isEqualTo(MissionErrorCode.MISSION_ALREADY_DELETED.message) + } + + @Test + @DisplayName("미션이 이미 완료되었다면 예외가 발생한다.") + fun shouldThrowExceptionWhenMissionAlreadyCompleted() { + // given + val missionIds = listOf(exploreMission1.id, exploreMission2.id) + exploreMission1.updateCompleted() + given(missionQueryRepository.findAllByIdsInFetchJoinStamp(missionIds)).willReturn(listOf(exploreMission1, exploreMission2)) + + // when + val exception = assertThrows { missionQueryService.getValidMissionsByIds(missionIds) } + + // then + assertThat(exception.message).isEqualTo(MissionErrorCode.MISSION_ALREADY_COMPLETED.message) + } + + @Test + @DisplayName("미션 ID 목록과 일치하는 미션 목록을 조회하고 반환한다.") + fun shouldReturnMissionsByIds() { + // given + val missionIds = listOf(exploreMission1.id, exploreMission2.id) + given(missionQueryRepository.findAllByIdsInFetchJoinStamp(missionIds)).willReturn(listOf(exploreMission1, exploreMission2)) + + // when + val result = missionQueryService.getValidMissionsByIds(missionIds) + + // then + assertThat(result).hasSize(2) + assertThat(result).contains(exploreMission1, exploreMission2) + } + } +} diff --git a/src/test/kotlin/com/ject/studytrip/mission/fixture/CreateMissionRequestFixture.kt b/src/test/kotlin/com/ject/studytrip/mission/fixture/CreateMissionRequestFixture.kt new file mode 100644 index 0000000..1f777a5 --- /dev/null +++ b/src/test/kotlin/com/ject/studytrip/mission/fixture/CreateMissionRequestFixture.kt @@ -0,0 +1,11 @@ +package com.ject.studytrip.mission.fixture + +import com.ject.studytrip.mission.presentation.dto.request.CreateMissionRequest + +class CreateMissionRequestFixture { + var name: String = "TEST 미션 이름" + + fun withName(name: String): CreateMissionRequestFixture = apply { this.name = name } + + fun build(): CreateMissionRequest = CreateMissionRequest(name) +} diff --git a/src/test/kotlin/com/ject/studytrip/mission/fixture/DailyMissionFixture.kt b/src/test/kotlin/com/ject/studytrip/mission/fixture/DailyMissionFixture.kt new file mode 100644 index 0000000..5a9ee0a --- /dev/null +++ b/src/test/kotlin/com/ject/studytrip/mission/fixture/DailyMissionFixture.kt @@ -0,0 +1,19 @@ +package com.ject.studytrip.mission.fixture + +import com.ject.studytrip.mission.domain.factory.DailyMissionFactory +import com.ject.studytrip.mission.domain.model.DailyMission +import com.ject.studytrip.mission.domain.model.Mission +import com.ject.studytrip.trip.domain.model.DailyGoal +import org.springframework.test.util.ReflectionTestUtils + +class DailyMissionFixture( + private val mission: Mission, + private val dailyGoal: DailyGoal, +) { + fun create(): DailyMission = DailyMissionFactory.create(mission, dailyGoal) + + fun createWithId(id: Long): DailyMission = + create().also { + ReflectionTestUtils.setField(it, "id", id) + } +} diff --git a/src/test/kotlin/com/ject/studytrip/mission/fixture/MissionFixture.kt b/src/test/kotlin/com/ject/studytrip/mission/fixture/MissionFixture.kt new file mode 100644 index 0000000..038aa6b --- /dev/null +++ b/src/test/kotlin/com/ject/studytrip/mission/fixture/MissionFixture.kt @@ -0,0 +1,19 @@ +package com.ject.studytrip.mission.fixture + +import com.ject.studytrip.mission.domain.factory.MissionFactory +import com.ject.studytrip.mission.domain.model.Mission +import com.ject.studytrip.stamp.domain.model.Stamp +import org.springframework.test.util.ReflectionTestUtils + +class MissionFixture( + private val stamp: Stamp, +) { + var name: String = "TEST 미션 이름" + + fun create(): Mission = MissionFactory.create(stamp, name) + + fun createWithId(id: Long): Mission = + create().also { + ReflectionTestUtils.setField(it, "id", id) + } +} diff --git a/src/test/kotlin/com/ject/studytrip/mission/fixture/UpdateMissionRequestFixture.kt b/src/test/kotlin/com/ject/studytrip/mission/fixture/UpdateMissionRequestFixture.kt new file mode 100644 index 0000000..acefe42 --- /dev/null +++ b/src/test/kotlin/com/ject/studytrip/mission/fixture/UpdateMissionRequestFixture.kt @@ -0,0 +1,11 @@ +package com.ject.studytrip.mission.fixture + +import com.ject.studytrip.mission.presentation.dto.request.UpdateMissionRequest + +class UpdateMissionRequestFixture { + var name: String = "TEST 새로운 미션 이름" + + fun withName(name: String): UpdateMissionRequestFixture = apply { this.name = name } + + fun build(): UpdateMissionRequest = UpdateMissionRequest(name) +} diff --git a/src/test/kotlin/com/ject/studytrip/mission/helper/DailyMissionTestHelper.kt b/src/test/kotlin/com/ject/studytrip/mission/helper/DailyMissionTestHelper.kt new file mode 100644 index 0000000..d34d147 --- /dev/null +++ b/src/test/kotlin/com/ject/studytrip/mission/helper/DailyMissionTestHelper.kt @@ -0,0 +1,23 @@ +package com.ject.studytrip.mission.helper + +import com.ject.studytrip.mission.domain.model.DailyMission +import com.ject.studytrip.mission.domain.model.Mission +import com.ject.studytrip.mission.domain.repository.DailyMissionRepository +import com.ject.studytrip.mission.fixture.DailyMissionFixture +import com.ject.studytrip.trip.domain.model.DailyGoal +import org.springframework.stereotype.Component + +@Component +class DailyMissionTestHelper( + private val dailyMissionRepository: DailyMissionRepository, +) { + fun saveDailyMission( + mission: Mission, + dailyGoal: DailyGoal, + ): DailyMission = dailyMissionRepository.save(DailyMissionFixture(mission, dailyGoal).create()) + + fun saveDeletedDailyMission( + mission: Mission, + dailyGoal: DailyGoal, + ): DailyMission = dailyMissionRepository.save(DailyMissionFixture(mission, dailyGoal).create().also { it.updateDeletedAt() }) +} diff --git a/src/test/kotlin/com/ject/studytrip/mission/helper/MissionTestHelper.kt b/src/test/kotlin/com/ject/studytrip/mission/helper/MissionTestHelper.kt new file mode 100644 index 0000000..d875e8e --- /dev/null +++ b/src/test/kotlin/com/ject/studytrip/mission/helper/MissionTestHelper.kt @@ -0,0 +1,18 @@ +package com.ject.studytrip.mission.helper + +import com.ject.studytrip.mission.domain.model.Mission +import com.ject.studytrip.mission.domain.repository.MissionRepository +import com.ject.studytrip.mission.fixture.MissionFixture +import com.ject.studytrip.stamp.domain.model.Stamp +import org.springframework.stereotype.Component + +@Component +class MissionTestHelper( + private val missionRepository: MissionRepository, +) { + fun saveMission(stamp: Stamp): Mission = missionRepository.save(MissionFixture(stamp).create()) + + fun saveDeletedMission(stamp: Stamp): Mission = missionRepository.save(MissionFixture(stamp).create().also { it.updateDeletedAt() }) + + fun saveCompletedMission(stamp: Stamp): Mission = missionRepository.save(MissionFixture(stamp).create().also { it.updateCompleted() }) +} diff --git a/src/test/kotlin/com/ject/studytrip/mission/presentation/controller/MissionControllerIntegrationTest.kt b/src/test/kotlin/com/ject/studytrip/mission/presentation/controller/MissionControllerIntegrationTest.kt new file mode 100644 index 0000000..4653de5 --- /dev/null +++ b/src/test/kotlin/com/ject/studytrip/mission/presentation/controller/MissionControllerIntegrationTest.kt @@ -0,0 +1,1166 @@ +package com.ject.studytrip.mission.presentation.controller + +import com.ject.studytrip.BaseIntegrationTest +import com.ject.studytrip.auth.domain.error.AuthErrorCode +import com.ject.studytrip.auth.fixture.TokenFixture +import com.ject.studytrip.auth.helper.TokenTestHelper +import com.ject.studytrip.global.exception.error.CommonErrorCode +import com.ject.studytrip.member.domain.model.Member +import com.ject.studytrip.member.domain.model.MemberRole +import com.ject.studytrip.member.helper.MemberTestHelper +import com.ject.studytrip.mission.domain.error.MissionErrorCode +import com.ject.studytrip.mission.domain.model.Mission +import com.ject.studytrip.mission.fixture.CreateMissionRequestFixture +import com.ject.studytrip.mission.fixture.UpdateMissionRequestFixture +import com.ject.studytrip.mission.helper.MissionTestHelper +import com.ject.studytrip.mission.presentation.dto.request.CreateMissionRequest +import com.ject.studytrip.mission.presentation.dto.request.UpdateMissionRequest +import com.ject.studytrip.stamp.domain.error.StampErrorCode +import com.ject.studytrip.stamp.domain.model.Stamp +import com.ject.studytrip.stamp.helper.StampTestHelper +import com.ject.studytrip.trip.domain.error.TripErrorCode +import com.ject.studytrip.trip.domain.model.Trip +import com.ject.studytrip.trip.domain.model.TripCategory +import com.ject.studytrip.trip.helper.TripTestHelper +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.http.HttpHeaders +import org.springframework.http.HttpStatus +import org.springframework.http.MediaType +import org.springframework.test.web.servlet.ResultActions +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status + +@DisplayName("MissionController 통합 테스트") +class MissionControllerIntegrationTest : BaseIntegrationTest() { + @Autowired private lateinit var memberTestHelper: MemberTestHelper + + @Autowired private lateinit var tokenTestHelper: TokenTestHelper + + @Autowired private lateinit var tripTestHelper: TripTestHelper + + @Autowired private lateinit var stampTestHelper: StampTestHelper + + @Autowired private lateinit var missionTestHelper: MissionTestHelper + + private lateinit var member: Member + private lateinit var token: String + + // 코스형 + private lateinit var courseTrip: Trip + private lateinit var courseStamp: Stamp + private lateinit var courseMission: Mission + + // 탐험형 + private lateinit var exploreTrip: Trip + private lateinit var exploreStamp: Stamp + private lateinit var exploreMission: Mission + + // 새로운 여행 + private lateinit var newTrip: Trip + + @BeforeEach + fun setUp() { + member = memberTestHelper.saveMember() + token = tokenTestHelper.createAccessToken(member.id.toString(), MemberRole.ROLE_USER.name) + + courseTrip = tripTestHelper.saveTrip(member, TripCategory.COURSE) + courseStamp = stampTestHelper.saveStamp(courseTrip, 1) + courseMission = missionTestHelper.saveMission(courseStamp) + + exploreTrip = tripTestHelper.saveTrip(member, TripCategory.EXPLORE) + exploreStamp = stampTestHelper.saveStamp(exploreTrip, 0) + exploreMission = missionTestHelper.saveMission(exploreStamp) + + val newMember = memberTestHelper.saveMember("test@gmail.com", "test") + newTrip = tripTestHelper.saveTrip(newMember, TripCategory.COURSE) + } + + companion object { + private const val BASE_MISSION_URL = "/api/trips/{tripId}/stamps/{stampId}/missions" + } + + @Nested + @DisplayName("미션 생성 API") + inner class CreateMission { + private val fixture = CreateMissionRequestFixture() + + private fun getResultActions( + token: String, + tripId: Any, + stampId: Any, + request: CreateMissionRequest, + ): ResultActions = + mockMvc.perform( + post(BASE_MISSION_URL, tripId, stampId) + .header(HttpHeaders.AUTHORIZATION, TokenFixture.authorization(token)) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request)), + ) + + @Test + @DisplayName("인증되지 않은 사용자라면 401 Unauthorized를 반환한다.") + fun shouldReturnUnauthorizedWhenUnauthenticated() { + // given + val request = fixture.build() + + // when + val resultActions = getResultActions("", courseTrip.id, courseStamp.id, request) + + // then + resultActions + .andExpect(status().isUnauthorized) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(AuthErrorCode.UNAUTHENTICATED.status.value())) + .andExpect(jsonPath("$.data.message").value(AuthErrorCode.UNAUTHENTICATED.message)) + } + + @Test + @DisplayName("PathVariable 여행 ID 타입이 올바르지 않으면 400 Bad Request를 반환한다.") + fun shouldReturnBadRequestWhenTripIdTypeMismatch() { + // given + val tripId = "abc" + val request = fixture.build() + + // when + val resultActions = getResultActions(token, tripId, courseStamp.id, request) + + // then + resultActions + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(CommonErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH.status.value())) + .andExpect(jsonPath("$.data.message").value(CommonErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH.message)) + } + + @Test + @DisplayName("PathVariable 스탬프 ID 타입이 올바르지 않으면 400 Bad Request를 반환한다.") + fun shouldReturnBadRequestWhenStampIdTypeMismatch() { + // given + val stampId = "abc" + val request = fixture.build() + + // when + val resultActions = getResultActions(token, courseTrip.id, stampId, request) + + // then + resultActions + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(CommonErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH.status.value())) + .andExpect(jsonPath("$.data.message").value(CommonErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH.message)) + } + + @Test + @DisplayName("CreateMissionRequest 이름이 비어있으면 400 Bad Request를 반환한다.") + fun shouldReturnBadRequestWhenRequestNameIsBlank() { + // given + val request = fixture.withName(" ").build() + + // when + val resultActions = getResultActions(token, courseTrip.id, courseStamp.id, request) + + // then + resultActions + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(CommonErrorCode.METHOD_ARGUMENT_NOT_VALID.status.value())) + .andExpect(jsonPath("$.data.message").value(CommonErrorCode.METHOD_ARGUMENT_NOT_VALID.message)) + } + + @Test + @DisplayName("여행이 존재하지 않으면 404 NotFound를 반환한다.") + fun shouldReturnNotFoundWhenTripDoesNotExist() { + // given + val tripId = -1L + val request = fixture.build() + + // when + val resultActions = getResultActions(token, tripId, courseStamp.id, request) + + // then + resultActions + .andExpect(status().isNotFound) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(TripErrorCode.TRIP_NOT_FOUND.status.value())) + .andExpect(jsonPath("$.data.message").value(TripErrorCode.TRIP_NOT_FOUND.message)) + } + + @Test + @DisplayName("여행의 소유자가 아니라면 403 Forbidden을 반환한다.") + fun shouldReturnForbiddenWhenMemberIsNotTripOwner() { + // given + val request = fixture.build() + + // when + val resultActions = getResultActions(token, newTrip.id, courseStamp.id, request) + + // then + resultActions + .andExpect(status().isForbidden) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(TripErrorCode.NOT_TRIP_OWNER.status.value())) + .andExpect(jsonPath("$.data.message").value(TripErrorCode.NOT_TRIP_OWNER.message)) + } + + @Test + @DisplayName("여행이 이미 삭제되었다면 400 Bad Request를 반환한다.") + fun shouldReturnBadRequestWhenTripAlreadyDeleted() { + // given + val deletedTrip = tripTestHelper.saveDeletedTrip(member, TripCategory.COURSE) + val request = fixture.build() + + // when + val resultActions = getResultActions(token, deletedTrip.id, courseStamp.id, request) + + // then + resultActions + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(TripErrorCode.TRIP_ALREADY_DELETED.status.value())) + .andExpect(jsonPath("$.data.message").value(TripErrorCode.TRIP_ALREADY_DELETED.message)) + } + + @Test + @DisplayName("여행이 이미 완료되었다면 400 Bad Request를 반환한다.") + fun shouldReturnBadRequestWhenTripAlreadyCompleted() { + // given + val completedTrip = tripTestHelper.saveCompletedTrip(member, TripCategory.COURSE) + val request = fixture.build() + + // when + val resultActions = getResultActions(token, completedTrip.id, courseStamp.id, request) + + // then + resultActions + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(TripErrorCode.TRIP_ALREADY_COMPLETED.status.value())) + .andExpect(jsonPath("$.data.message").value(TripErrorCode.TRIP_ALREADY_COMPLETED.message)) + } + + @Test + @DisplayName("스탬프가 존재하지 않으면 404 NotFound를 반환한다.") + fun shouldReturnNotFoundWhenStampDoesNotExist() { + // given + val stampId = -1L + val request = fixture.build() + + // when + val resultActions = getResultActions(token, courseTrip.id, stampId, request) + + // then + resultActions + .andExpect(status().isNotFound) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(StampErrorCode.STAMP_NOT_FOUND.status.value())) + .andExpect(jsonPath("$.data.message").value(StampErrorCode.STAMP_NOT_FOUND.message)) + } + + @Test + @DisplayName("스탬프가 요청한 여행에 속하지 않으면 403 Forbidden을 반환한다.") + fun shouldReturnForbiddenWhenStampNotBelongToTrip() { + // given + val request = fixture.build() + + // when + val resultActions = getResultActions(token, courseTrip.id, exploreStamp.id, request) + + // then + resultActions + .andExpect(status().isForbidden) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(StampErrorCode.STAMP_NOT_BELONGS_TO_TRIP.status.value())) + .andExpect(jsonPath("$.data.message").value(StampErrorCode.STAMP_NOT_BELONGS_TO_TRIP.message)) + } + + @Test + @DisplayName("스탬프가 이미 삭제되었다면 400 Bad Request를 반환한다.") + fun shouldReturnBadRequestWhenStampAlreadyDeleted() { + // given + val deletedStamp = stampTestHelper.saveDeletedStamp(courseTrip, 3) + val request = fixture.build() + + // when + val resultActions = getResultActions(token, courseTrip.id, deletedStamp.id, request) + + // then + resultActions + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(StampErrorCode.STAMP_ALREADY_DELETED.status.value())) + .andExpect(jsonPath("$.data.message").value(StampErrorCode.STAMP_ALREADY_DELETED.message)) + } + + @Test + @DisplayName("스탬프가 이미 완료되었다면 400 Bad Request를 반환한다.") + fun shouldReturnBadRequestWhenStampAlreadyCompleted() { + // given + val completedStamp = stampTestHelper.saveCompletedStamp(courseTrip, 3) + val request = fixture.build() + + // when + val resultActions = getResultActions(token, courseTrip.id, completedStamp.id, request) + + // then + resultActions + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(StampErrorCode.STAMP_ALREADY_COMPLETED.status.value())) + .andExpect(jsonPath("$.data.message").value(StampErrorCode.STAMP_ALREADY_COMPLETED.message)) + } + + @Test + @DisplayName("유효한 요청이 들어오면 미션을 생성하고 반환한다.") + fun shouldCreateAndReturnMissionWhenRequestIsValid() { + // given + val request = fixture.build() + + // when + val resultActions = getResultActions(token, courseTrip.id, courseStamp.id, request) + + // then + resultActions + .andExpect(status().isCreated) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.status").value(HttpStatus.CREATED.value())) + .andExpect(jsonPath("$.data.missionId").isNumber) + } + } + + @Nested + @DisplayName("미션 수정 API") + inner class UpdateMission { + private val fixture = UpdateMissionRequestFixture() + + private fun getResultActions( + token: String, + tripId: Any, + stampId: Any, + missionId: Any, + request: UpdateMissionRequest, + ): ResultActions = + mockMvc.perform( + patch("$BASE_MISSION_URL/{missionId}", tripId, stampId, missionId) + .header(HttpHeaders.AUTHORIZATION, TokenFixture.authorization(token)) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request)), + ) + + @Test + @DisplayName("인증되지 않은 사용자라면 401 Unauthorized를 반환한다.") + fun shouldReturnUnauthorizedWhenUnauthenticated() { + // given + val request = fixture.build() + + // when + val resultActions = getResultActions("", courseTrip.id, courseStamp.id, courseMission.id, request) + + // then + resultActions + .andExpect(status().isUnauthorized) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(AuthErrorCode.UNAUTHENTICATED.status.value())) + .andExpect(jsonPath("$.data.message").value(AuthErrorCode.UNAUTHENTICATED.message)) + } + + @Test + @DisplayName("PathVariable 여행 ID 타입이 올바르지 않으면 400 Bad Request를 반환한다.") + fun shouldReturnBadRequestWhenTripIdTypeMismatch() { + // given + val tripId = "abc" + val request = fixture.build() + + // when + val resultActions = getResultActions(token, tripId, courseStamp.id, courseMission.id, request) + + // then + resultActions + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(CommonErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH.status.value())) + .andExpect(jsonPath("$.data.message").value(CommonErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH.message)) + } + + @Test + @DisplayName("PathVariable 스탬프 ID 타입이 올바르지 않으면 400 Bad Request를 반환한다.") + fun shouldReturnBadRequestWhenStampIdTypeMismatch() { + // given + val stampId = "abc" + val request = fixture.build() + + // when + val resultActions = getResultActions(token, courseTrip.id, stampId, courseMission.id, request) + + // then + resultActions + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(CommonErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH.status.value())) + .andExpect(jsonPath("$.data.message").value(CommonErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH.message)) + } + + @Test + @DisplayName("PathVariable 미션 ID 타입이 올바르지 않으면 400 Bad Request를 반환한다.") + fun shouldReturnBadRequestWhenMissionIdTypeMismatch() { + // given + val missionId = "abc" + val request = fixture.build() + + // when + val resultActions = getResultActions(token, courseTrip.id, courseStamp.id, missionId, request) + + // then + resultActions + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(CommonErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH.status.value())) + .andExpect(jsonPath("$.data.message").value(CommonErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH.message)) + } + + @Test + @DisplayName("UpdateMissionRequest 이름이 비어있으면 400 Bad Request를 반환한다.") + fun shouldReturnBadRequestWhenRequestNameIsBlank() { + // given + val request = fixture.withName(" ").build() + + // when + val resultActions = getResultActions(token, courseTrip.id, courseStamp.id, courseMission.id, request) + + // then + resultActions + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(CommonErrorCode.METHOD_ARGUMENT_NOT_VALID.status.value())) + .andExpect(jsonPath("$.data.message").value(CommonErrorCode.METHOD_ARGUMENT_NOT_VALID.message)) + } + + @Test + @DisplayName("여행이 존재하지 않으면 404 NotFound를 반환한다.") + fun shouldReturnNotFoundWhenTripDoesNotExist() { + // given + val tripId = -1L + val request = fixture.build() + + // when + val resultActions = getResultActions(token, tripId, courseStamp.id, courseMission.id, request) + + // then + resultActions + .andExpect(status().isNotFound) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(TripErrorCode.TRIP_NOT_FOUND.status.value())) + .andExpect(jsonPath("$.data.message").value(TripErrorCode.TRIP_NOT_FOUND.message)) + } + + @Test + @DisplayName("여행의 소유자가 아니라면 403 Forbidden을 반환한다.") + fun shouldReturnForbiddenWhenMemberIsNotTripOwner() { + // given + val request = fixture.build() + + // when + val resultActions = getResultActions(token, newTrip.id, courseStamp.id, courseMission.id, request) + + // then + resultActions + .andExpect(status().isForbidden) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(TripErrorCode.NOT_TRIP_OWNER.status.value())) + .andExpect(jsonPath("$.data.message").value(TripErrorCode.NOT_TRIP_OWNER.message)) + } + + @Test + @DisplayName("여행이 이미 삭제되었다면 400 Bad Request를 반환한다.") + fun shouldReturnBadRequestWhenTripAlreadyDeleted() { + // given + val deletedTrip = tripTestHelper.saveDeletedTrip(member, TripCategory.COURSE) + val request = fixture.build() + + // when + val resultActions = getResultActions(token, deletedTrip.id, courseStamp.id, courseMission.id, request) + + // then + resultActions + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(TripErrorCode.TRIP_ALREADY_DELETED.status.value())) + .andExpect(jsonPath("$.data.message").value(TripErrorCode.TRIP_ALREADY_DELETED.message)) + } + + @Test + @DisplayName("여행이 이미 완료되었다면 400 Bad Request를 반환한다.") + fun shouldReturnBadRequestWhenTripAlreadyCompleted() { + // given + val completedTrip = tripTestHelper.saveCompletedTrip(member, TripCategory.COURSE) + val request = fixture.build() + + // when + val resultActions = getResultActions(token, completedTrip.id, courseStamp.id, courseMission.id, request) + + // then + resultActions + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(TripErrorCode.TRIP_ALREADY_COMPLETED.status.value())) + .andExpect(jsonPath("$.data.message").value(TripErrorCode.TRIP_ALREADY_COMPLETED.message)) + } + + @Test + @DisplayName("스탬프가 존재하지 않으면 404 NotFound를 반환한다.") + fun shouldReturnNotFoundWhenStampDoesNotExist() { + // given + val stampId = -1L + val request = fixture.build() + + // when + val resultActions = getResultActions(token, courseTrip.id, stampId, courseMission.id, request) + + // then + resultActions + .andExpect(status().isNotFound) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(StampErrorCode.STAMP_NOT_FOUND.status.value())) + .andExpect(jsonPath("$.data.message").value(StampErrorCode.STAMP_NOT_FOUND.message)) + } + + @Test + @DisplayName("스탬프가 요청한 여행에 속하지 않으면 403 Forbidden을 반환한다.") + fun shouldReturnForbiddenWhenStampNotBelongToTrip() { + // given + val request = fixture.build() + + // when + val resultActions = getResultActions(token, courseTrip.id, exploreStamp.id, courseMission.id, request) + + // then + resultActions + .andExpect(status().isForbidden) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(StampErrorCode.STAMP_NOT_BELONGS_TO_TRIP.status.value())) + .andExpect(jsonPath("$.data.message").value(StampErrorCode.STAMP_NOT_BELONGS_TO_TRIP.message)) + } + + @Test + @DisplayName("스탬프가 이미 삭제되었다면 400 Bad Request를 반환한다.") + fun shouldReturnBadRequestWhenStampAlreadyDeleted() { + // given + val deletedStamp = stampTestHelper.saveDeletedStamp(courseTrip, 3) + val request = fixture.build() + + // when + val resultActions = getResultActions(token, courseTrip.id, deletedStamp.id, courseMission.id, request) + + // then + resultActions + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(StampErrorCode.STAMP_ALREADY_DELETED.status.value())) + .andExpect(jsonPath("$.data.message").value(StampErrorCode.STAMP_ALREADY_DELETED.message)) + } + + @Test + @DisplayName("스탬프가 이미 완료되었다면 400 Bad Request를 반환한다.") + fun shouldReturnBadRequestWhenStampAlreadyCompleted() { + // given + val completedStamp = stampTestHelper.saveCompletedStamp(courseTrip, 3) + val request = fixture.build() + + // when + val resultActions = getResultActions(token, courseTrip.id, completedStamp.id, courseMission.id, request) + + // then + resultActions + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(StampErrorCode.STAMP_ALREADY_COMPLETED.status.value())) + .andExpect(jsonPath("$.data.message").value(StampErrorCode.STAMP_ALREADY_COMPLETED.message)) + } + + @Test + @DisplayName("미션이 존재하지 않으면 404 NotFound를 반환한다.") + fun shouldReturnNotFoundWhenMissionDoesNotExist() { + // given + val missionId = -1L + val request = fixture.build() + + // when + val resultActions = getResultActions(token, courseTrip.id, courseStamp.id, missionId, request) + + // then + resultActions + .andExpect(status().isNotFound) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(MissionErrorCode.MISSION_NOT_FOUND.status.value())) + .andExpect(jsonPath("$.data.message").value(MissionErrorCode.MISSION_NOT_FOUND.message)) + } + + @Test + @DisplayName("미션이 요청한 스탬프에 속하지 않으면 403 Forbidden을 반환한다.") + fun shouldReturnForbiddenWhenMissionNotBelongToStamp() { + // given + val request = fixture.build() + + // when + val resultActions = getResultActions(token, courseTrip.id, courseStamp.id, exploreMission.id, request) + + // then + resultActions + .andExpect(status().isForbidden) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(MissionErrorCode.MISSION_NOT_BELONGS_TO_STAMP.status.value())) + .andExpect(jsonPath("$.data.message").value(MissionErrorCode.MISSION_NOT_BELONGS_TO_STAMP.message)) + } + + @Test + @DisplayName("미션이 이미 삭제되었다면 400 Bad Request를 반환한다.") + fun shouldReturnBadRequestWhenMissionAlreadyDeleted() { + // given + val deletedMission = missionTestHelper.saveDeletedMission(courseStamp) + val request = fixture.build() + + // when + val resultActions = getResultActions(token, courseTrip.id, courseStamp.id, deletedMission.id, request) + + // then + resultActions + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(MissionErrorCode.MISSION_ALREADY_DELETED.status.value())) + .andExpect(jsonPath("$.data.message").value(MissionErrorCode.MISSION_ALREADY_DELETED.message)) + } + + @Test + @DisplayName("미션이 이미 완료되었다면 400 Bad Request를 반환한다.") + fun shouldReturnBadRequestWhenMissionAlreadyCompleted() { + // given + val completedMission = missionTestHelper.saveCompletedMission(courseStamp) + val request = fixture.build() + + // when + val resultActions = getResultActions(token, courseTrip.id, courseStamp.id, completedMission.id, request) + + // then + resultActions + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(MissionErrorCode.MISSION_ALREADY_COMPLETED.status.value())) + .andExpect(jsonPath("$.data.message").value(MissionErrorCode.MISSION_ALREADY_COMPLETED.message)) + } + + @Test + @DisplayName("유효한 요청이 들어오면 미션 이름을 수정한다.") + fun shouldUpdateMissionNameWhenRequestIsValid() { + // given + val request = fixture.build() + + // when + val resultActions = getResultActions(token, courseTrip.id, courseStamp.id, courseMission.id, request) + + // then + resultActions + .andExpect(status().isOk) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.status").value(HttpStatus.OK.value())) + } + } + + @Nested + @DisplayName("미션 삭제 API") + inner class DeleteMission { + private fun getResultActions( + token: String, + tripId: Any, + stampId: Any, + missionId: Any, + ): ResultActions = + mockMvc.perform( + delete("$BASE_MISSION_URL/{missionId}", tripId, stampId, missionId) + .header(HttpHeaders.AUTHORIZATION, TokenFixture.authorization(token)), + ) + + @Test + @DisplayName("인증되지 않은 사용자라면 401 Unauthorized를 반환한다.") + fun shouldReturnUnauthorizedWhenUnauthenticated() { + // when + val resultActions = getResultActions("", courseTrip.id, courseStamp.id, courseMission.id) + + // then + resultActions + .andExpect(status().isUnauthorized) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(AuthErrorCode.UNAUTHENTICATED.status.value())) + .andExpect(jsonPath("$.data.message").value(AuthErrorCode.UNAUTHENTICATED.message)) + } + + @Test + @DisplayName("PathVariable 여행 ID 타입이 올바르지 않으면 400 Bad Request를 반환한다.") + fun shouldReturnBadRequestWhenTripIdTypeMismatch() { + // given + val tripId = "abc" + // when + val resultActions = getResultActions(token, tripId, courseStamp.id, courseMission.id) + + // then + resultActions + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(CommonErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH.status.value())) + .andExpect(jsonPath("$.data.message").value(CommonErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH.message)) + } + + @Test + @DisplayName("PathVariable 스탬프 ID 타입이 올바르지 않으면 400 Bad Request를 반환한다.") + fun shouldReturnBadRequestWhenStampIdTypeMismatch() { + // given + val stampId = "abc" + + // when + val resultActions = getResultActions(token, courseTrip.id, stampId, courseMission.id) + + // then + resultActions + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(CommonErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH.status.value())) + .andExpect(jsonPath("$.data.message").value(CommonErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH.message)) + } + + @Test + @DisplayName("PathVariable 미션 ID 타입이 올바르지 않으면 400 Bad Request를 반환한다.") + fun shouldReturnBadRequestWhenMissionIdTypeMismatch() { + // given + val missionId = "abc" + + // when + val resultActions = getResultActions(token, courseTrip.id, courseStamp.id, missionId) + + // then + resultActions + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(CommonErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH.status.value())) + .andExpect(jsonPath("$.data.message").value(CommonErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH.message)) + } + + @Test + @DisplayName("여행이 존재하지 않으면 404 NotFound를 반환한다.") + fun shouldReturnNotFoundWhenTripDoesNotExist() { + // given + val tripId = -1L + + // when + val resultActions = getResultActions(token, tripId, courseStamp.id, courseMission.id) + + // then + resultActions + .andExpect(status().isNotFound) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(TripErrorCode.TRIP_NOT_FOUND.status.value())) + .andExpect(jsonPath("$.data.message").value(TripErrorCode.TRIP_NOT_FOUND.message)) + } + + @Test + @DisplayName("여행의 소유자가 아니라면 403 Forbidden을 반환한다.") + fun shouldReturnForbiddenWhenMemberIsNotTripOwner() { + // when + val resultActions = getResultActions(token, newTrip.id, courseStamp.id, courseMission.id) + + // then + resultActions + .andExpect(status().isForbidden) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(TripErrorCode.NOT_TRIP_OWNER.status.value())) + .andExpect(jsonPath("$.data.message").value(TripErrorCode.NOT_TRIP_OWNER.message)) + } + + @Test + @DisplayName("여행이 이미 삭제되었다면 400 Bad Request를 반환한다.") + fun shouldReturnBadRequestWhenTripAlreadyDeleted() { + // given + val deletedTrip = tripTestHelper.saveDeletedTrip(member, TripCategory.COURSE) + + // when + val resultActions = getResultActions(token, deletedTrip.id, courseStamp.id, courseMission.id) + + // then + resultActions + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(TripErrorCode.TRIP_ALREADY_DELETED.status.value())) + .andExpect(jsonPath("$.data.message").value(TripErrorCode.TRIP_ALREADY_DELETED.message)) + } + + @Test + @DisplayName("여행이 이미 완료되었다면 400 Bad Request를 반환한다.") + fun shouldReturnBadRequestWhenTripAlreadyCompleted() { + // given + val completedTrip = tripTestHelper.saveCompletedTrip(member, TripCategory.COURSE) + + // when + val resultActions = getResultActions(token, completedTrip.id, courseStamp.id, courseMission.id) + + // then + resultActions + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(TripErrorCode.TRIP_ALREADY_COMPLETED.status.value())) + .andExpect(jsonPath("$.data.message").value(TripErrorCode.TRIP_ALREADY_COMPLETED.message)) + } + + @Test + @DisplayName("스탬프가 존재하지 않으면 404 NotFound를 반환한다.") + fun shouldReturnNotFoundWhenStampDoesNotExist() { + // given + val stampId = -1L + + // when + val resultActions = getResultActions(token, courseTrip.id, stampId, courseMission.id) + + // then + resultActions + .andExpect(status().isNotFound) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(StampErrorCode.STAMP_NOT_FOUND.status.value())) + .andExpect(jsonPath("$.data.message").value(StampErrorCode.STAMP_NOT_FOUND.message)) + } + + @Test + @DisplayName("스탬프가 요청한 여행에 속하지 않으면 403 Forbidden을 반환한다.") + fun shouldReturnForbiddenWhenStampNotBelongToTrip() { + // when + val resultActions = getResultActions(token, courseTrip.id, exploreStamp.id, courseMission.id) + + // then + resultActions + .andExpect(status().isForbidden) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(StampErrorCode.STAMP_NOT_BELONGS_TO_TRIP.status.value())) + .andExpect(jsonPath("$.data.message").value(StampErrorCode.STAMP_NOT_BELONGS_TO_TRIP.message)) + } + + @Test + @DisplayName("스탬프가 이미 삭제되었다면 400 Bad Request를 반환한다.") + fun shouldReturnBadRequestWhenStampAlreadyDeleted() { + // given + val deletedStamp = stampTestHelper.saveDeletedStamp(courseTrip, 3) + + // when + val resultActions = getResultActions(token, courseTrip.id, deletedStamp.id, courseMission.id) + + // then + resultActions + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(StampErrorCode.STAMP_ALREADY_DELETED.status.value())) + .andExpect(jsonPath("$.data.message").value(StampErrorCode.STAMP_ALREADY_DELETED.message)) + } + + @Test + @DisplayName("스탬프가 이미 완료되었다면 400 Bad Request를 반환한다.") + fun shouldReturnBadRequestWhenStampAlreadyCompleted() { + // given + val completedStamp = stampTestHelper.saveCompletedStamp(courseTrip, 3) + + // when + val resultActions = getResultActions(token, courseTrip.id, completedStamp.id, courseMission.id) + + // then + resultActions + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(StampErrorCode.STAMP_ALREADY_COMPLETED.status.value())) + .andExpect(jsonPath("$.data.message").value(StampErrorCode.STAMP_ALREADY_COMPLETED.message)) + } + + @Test + @DisplayName("미션이 존재하지 않으면 404 NotFound를 반환한다.") + fun shouldReturnNotFoundWhenMissionDoesNotExist() { + // given + val missionId = -1L + + // when + val resultActions = getResultActions(token, courseTrip.id, courseStamp.id, missionId) + + // then + resultActions + .andExpect(status().isNotFound) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(MissionErrorCode.MISSION_NOT_FOUND.status.value())) + .andExpect(jsonPath("$.data.message").value(MissionErrorCode.MISSION_NOT_FOUND.message)) + } + + @Test + @DisplayName("미션이 요청한 스탬프에 속하지 않으면 403 Forbidden을 반환한다.") + fun shouldReturnForbiddenWhenMissionNotBelongToStamp() { + // when + val resultActions = getResultActions(token, courseTrip.id, courseStamp.id, exploreMission.id) + + // then + resultActions + .andExpect(status().isForbidden) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(MissionErrorCode.MISSION_NOT_BELONGS_TO_STAMP.status.value())) + .andExpect(jsonPath("$.data.message").value(MissionErrorCode.MISSION_NOT_BELONGS_TO_STAMP.message)) + } + + @Test + @DisplayName("미션이 이미 삭제되었다면 400 Bad Request를 반환한다.") + fun shouldReturnBadRequestWhenMissionAlreadyDeleted() { + // given + val deletedMission = missionTestHelper.saveDeletedMission(courseStamp) + + // when + val resultActions = getResultActions(token, courseTrip.id, courseStamp.id, deletedMission.id) + + // then + resultActions + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(MissionErrorCode.MISSION_ALREADY_DELETED.status.value())) + .andExpect(jsonPath("$.data.message").value(MissionErrorCode.MISSION_ALREADY_DELETED.message)) + } + + @Test + @DisplayName("미션이 이미 완료되었다면 400 Bad Request를 반환한다.") + fun shouldReturnBadRequestWhenMissionAlreadyCompleted() { + // given + val completedMission = missionTestHelper.saveCompletedMission(courseStamp) + + // when + val resultActions = getResultActions(token, courseTrip.id, courseStamp.id, completedMission.id) + + // then + resultActions + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(MissionErrorCode.MISSION_ALREADY_COMPLETED.status.value())) + .andExpect(jsonPath("$.data.message").value(MissionErrorCode.MISSION_ALREADY_COMPLETED.message)) + } + + @Test + @DisplayName("특정 미션을 삭제한다.") + fun shouldDeleteMission() { + // when + val resultActions = getResultActions(token, courseTrip.id, courseStamp.id, courseMission.id) + + // then + resultActions + .andExpect(status().isOk) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.status").value(HttpStatus.OK.value())) + } + } + + @Nested + @DisplayName("미션 목록 조회 API") + inner class LoadMissionsByStamp { + private fun getResultActions( + token: String, + tripId: Any, + stampId: Any, + ): ResultActions = + mockMvc.perform( + get(BASE_MISSION_URL, tripId, stampId) + .header(HttpHeaders.AUTHORIZATION, TokenFixture.authorization(token)), + ) + + @Test + @DisplayName("인증되지 않은 사용자라면 401 Unauthorized를 반환한다.") + fun shouldReturnUnauthorizedWhenUnauthenticated() { + // when + val resultActions = getResultActions("", courseTrip.id, courseStamp.id) + + // then + resultActions + .andExpect(status().isUnauthorized) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(AuthErrorCode.UNAUTHENTICATED.status.value())) + .andExpect(jsonPath("$.data.message").value(AuthErrorCode.UNAUTHENTICATED.message)) + } + + @Test + @DisplayName("PathVariable 여행 ID 타입이 올바르지 않으면 400 Bad Request를 반환한다.") + fun shouldReturnBadRequestWhenTripIdTypeMismatch() { + // given + val tripId = "abc" + // when + val resultActions = getResultActions(token, tripId, courseStamp.id) + + // then + resultActions + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(CommonErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH.status.value())) + .andExpect(jsonPath("$.data.message").value(CommonErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH.message)) + } + + @Test + @DisplayName("PathVariable 스탬프 ID 타입이 올바르지 않으면 400 Bad Request를 반환한다.") + fun shouldReturnBadRequestWhenStampIdTypeMismatch() { + // given + val stampId = "abc" + + // when + val resultActions = getResultActions(token, courseTrip.id, stampId) + + // then + resultActions + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(CommonErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH.status.value())) + .andExpect(jsonPath("$.data.message").value(CommonErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH.message)) + } + + @Test + @DisplayName("여행이 존재하지 않으면 404 NotFound를 반환한다.") + fun shouldReturnNotFoundWhenTripDoesNotExist() { + // given + val tripId = -1L + + // when + val resultActions = getResultActions(token, tripId, courseStamp.id) + + // then + resultActions + .andExpect(status().isNotFound) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(TripErrorCode.TRIP_NOT_FOUND.status.value())) + .andExpect(jsonPath("$.data.message").value(TripErrorCode.TRIP_NOT_FOUND.message)) + } + + @Test + @DisplayName("여행의 소유자가 아니라면 403 Forbidden을 반환한다.") + fun shouldReturnForbiddenWhenMemberIsNotTripOwner() { + // when + val resultActions = getResultActions(token, newTrip.id, courseStamp.id) + + // then + resultActions + .andExpect(status().isForbidden) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(TripErrorCode.NOT_TRIP_OWNER.status.value())) + .andExpect(jsonPath("$.data.message").value(TripErrorCode.NOT_TRIP_OWNER.message)) + } + + @Test + @DisplayName("여행이 이미 삭제되었다면 400 Bad Request를 반환한다.") + fun shouldReturnBadRequestWhenTripAlreadyDeleted() { + // given + val deletedTrip = tripTestHelper.saveDeletedTrip(member, TripCategory.COURSE) + + // when + val resultActions = getResultActions(token, deletedTrip.id, courseStamp.id) + + // then + resultActions + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(TripErrorCode.TRIP_ALREADY_DELETED.status.value())) + .andExpect(jsonPath("$.data.message").value(TripErrorCode.TRIP_ALREADY_DELETED.message)) + } + + @Test + @DisplayName("여행이 이미 완료되었다면 400 Bad Request를 반환한다.") + fun shouldReturnBadRequestWhenTripAlreadyCompleted() { + // given + val completedTrip = tripTestHelper.saveCompletedTrip(member, TripCategory.COURSE) + + // when + val resultActions = getResultActions(token, completedTrip.id, courseStamp.id) + + // then + resultActions + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(TripErrorCode.TRIP_ALREADY_COMPLETED.status.value())) + .andExpect(jsonPath("$.data.message").value(TripErrorCode.TRIP_ALREADY_COMPLETED.message)) + } + + @Test + @DisplayName("스탬프가 존재하지 않으면 404 NotFound를 반환한다.") + fun shouldReturnNotFoundWhenStampDoesNotExist() { + // given + val stampId = -1L + + // when + val resultActions = getResultActions(token, courseTrip.id, stampId) + + // then + resultActions + .andExpect(status().isNotFound) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(StampErrorCode.STAMP_NOT_FOUND.status.value())) + .andExpect(jsonPath("$.data.message").value(StampErrorCode.STAMP_NOT_FOUND.message)) + } + + @Test + @DisplayName("스탬프가 요청한 여행에 속하지 않으면 403 Forbidden을 반환한다.") + fun shouldReturnForbiddenWhenStampNotBelongToTrip() { + // when + val resultActions = getResultActions(token, courseTrip.id, exploreStamp.id) + + // then + resultActions + .andExpect(status().isForbidden) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(StampErrorCode.STAMP_NOT_BELONGS_TO_TRIP.status.value())) + .andExpect(jsonPath("$.data.message").value(StampErrorCode.STAMP_NOT_BELONGS_TO_TRIP.message)) + } + + @Test + @DisplayName("스탬프가 이미 삭제되었다면 400 Bad Request를 반환한다.") + fun shouldReturnBadRequestWhenStampAlreadyDeleted() { + // given + val deletedStamp = stampTestHelper.saveDeletedStamp(courseTrip, 3) + + // when + val resultActions = getResultActions(token, courseTrip.id, deletedStamp.id) + + // then + resultActions + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(StampErrorCode.STAMP_ALREADY_DELETED.status.value())) + .andExpect(jsonPath("$.data.message").value(StampErrorCode.STAMP_ALREADY_DELETED.message)) + } + + @Test + @DisplayName("스탬프가 이미 완료되었다면 400 Bad Request를 반환한다.") + fun shouldReturnBadRequestWhenStampAlreadyCompleted() { + // given + val completedStamp = stampTestHelper.saveCompletedStamp(courseTrip, 3) + + // when + val resultActions = getResultActions(token, courseTrip.id, completedStamp.id) + + // then + resultActions + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(StampErrorCode.STAMP_ALREADY_COMPLETED.status.value())) + .andExpect(jsonPath("$.data.message").value(StampErrorCode.STAMP_ALREADY_COMPLETED.message)) + } + + @Test + @DisplayName("특정 스탬프의 미션 목록을 조회하고 반환한다.") + fun shouldReturnMissionsByStamp() { + // when + val resultActions = getResultActions(token, courseTrip.id, courseStamp.id) + + // then + resultActions + .andExpect(status().isOk) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.status").value(HttpStatus.OK.value())) + .andExpect(jsonPath("$.data").isNotEmpty) + } + } +} diff --git a/src/test/kotlin/com/ject/studytrip/pomodoro/application/service/PomodoroCommandServiceTest.kt b/src/test/kotlin/com/ject/studytrip/pomodoro/application/service/PomodoroCommandServiceTest.kt index a8f9878..eb448c7 100644 --- a/src/test/kotlin/com/ject/studytrip/pomodoro/application/service/PomodoroCommandServiceTest.kt +++ b/src/test/kotlin/com/ject/studytrip/pomodoro/application/service/PomodoroCommandServiceTest.kt @@ -53,7 +53,7 @@ class PomodoroCommandServiceTest : BaseUnitTest() { inner class CreatePomodoro { @Test @DisplayName("유효한 요청이 들어오면 뽀모도로를 생성하고 반환한다.") - fun shouldReturnPomodoroWhenRequestIsValid() { + fun shouldCreateAndReturnPomodoroWhenRequestIsValid() { // given val request = CreatePomodoroRequestFixture() @@ -157,7 +157,7 @@ class PomodoroCommandServiceTest : BaseUnitTest() { @DisplayName("hardDeletePomodoros 메서드는") inner class HardDeletePomodoros { @Test - @DisplayName("삭제된 뽀모도로가 존재하지 않으면 0을 반환한다.") + @DisplayName("삭제된 뽀모도로가 하나라도 존재하지 않으면 0을 반환한다.") fun shouldReturnZeroWhenDeletedPomodorosDoNotExist() { // given given(pomodoroCommandRepository.deleteAllByDeletedAtIsNotNull()).willReturn(0L) @@ -170,7 +170,7 @@ class PomodoroCommandServiceTest : BaseUnitTest() { } @Test - @DisplayName("삭제된 뽀모도로가 존재하면 해당 개수를 반환한다.") + @DisplayName("삭제된 뽀모도로가 하나라도 존재하면 해당 개수를 반환한다.") fun shouldReturnCountWhenDeletedPomodorosExist() { // given given(pomodoroCommandRepository.deleteAllByDeletedAtIsNotNull()).willReturn(5L) @@ -187,7 +187,7 @@ class PomodoroCommandServiceTest : BaseUnitTest() { @DisplayName("hardDeletePomodorosOwnedByDeletedDailyGoal 메서드는") inner class HardDeletePomodorosOwnedByDeletedDailyGoal { @Test - @DisplayName("삭제된 데일리 목표가 소유한 뽀모도로가 존재하지 않으면 0을 반환한다.") + @DisplayName("삭제된 데일리 목표가 소유한 뽀모도로가 하나라도 존재하지 않으면 0을 반환한다.") fun shouldReturnZeroWhenPomodorosOwnedByDeletedDailyGoalDoNotExist() { // given given(pomodoroCommandRepository.deleteAllByDeletedDailyGoalOwner()).willReturn(0L) @@ -200,7 +200,7 @@ class PomodoroCommandServiceTest : BaseUnitTest() { } @Test - @DisplayName("삭제된 데일리 목표가 소유한 뽀모도로가 존재하면 해당 개수를 반환한다.") + @DisplayName("삭제된 데일리 목표가 소유한 뽀모도로가 하나라도 존재하면 해당 개수를 반환한다.") fun shouldReturnCountWhenPomodorosOwnedByDeletedDailyGoalExist() { // given given(pomodoroCommandRepository.deleteAllByDeletedDailyGoalOwner()).willReturn(5L) @@ -214,31 +214,31 @@ class PomodoroCommandServiceTest : BaseUnitTest() { } @Nested - @DisplayName("hardDeletePomodorosByMember 메서드는") - inner class HardDeletePomodorosByMember { + @DisplayName("hardDeletePomodorosOwnedByMember 메서드는") + inner class HardDeletePomodorosOwnedByMember { @Test - @DisplayName("특정 멤버가 소유한 뽀모도로가 존재하지 않으면 0을 반환한다.") + @DisplayName("특정 멤버가 소유한 뽀모도로가 하나라도 존재하지 않으면 0을 반환한다.") fun shouldReturnZeroWhenPomodorosOwnedByMemberDoNotExist() { // given - val memberId = -1L + val memberId = member.id given(pomodoroCommandRepository.deleteAllByMemberId(memberId)).willReturn(0L) // when - val result = pomodoroCommandService.hardDeletePomodorosByMember(memberId) + val result = pomodoroCommandService.hardDeletePomodorosOwnedByMember(memberId) // then assertThat(result).isEqualTo(0L) } @Test - @DisplayName("특정 멤버가 소유한 뽀모도로가 존재하면 해당 개수를 반환한다.") + @DisplayName("특정 멤버가 소유한 뽀모도로가 하나라도 존재하면 해당 개수를 반환한다.") fun shouldReturnCountWhenPomodorosOwnedByMemberExist() { // given val memberId = member.id given(pomodoroCommandRepository.deleteAllByMemberId(memberId)).willReturn(5L) // when - val result = pomodoroCommandService.hardDeletePomodorosByMember(memberId) + val result = pomodoroCommandService.hardDeletePomodorosOwnedByMember(memberId) // then assertThat(result).isEqualTo(5L) diff --git a/src/test/kotlin/com/ject/studytrip/pomodoro/application/service/PomodoroQueryServiceTest.kt b/src/test/kotlin/com/ject/studytrip/pomodoro/application/service/PomodoroQueryServiceTest.kt index 3a5cfa9..bee498a 100644 --- a/src/test/kotlin/com/ject/studytrip/pomodoro/application/service/PomodoroQueryServiceTest.kt +++ b/src/test/kotlin/com/ject/studytrip/pomodoro/application/service/PomodoroQueryServiceTest.kt @@ -48,8 +48,8 @@ class PomodoroQueryServiceTest : BaseUnitTest() { } @Nested - @DisplayName("getValidPomodoroByDailyGoal 메서드는") - inner class GetValidPomodoroByDailyGoal { + @DisplayName("getValidPomodoroByDailyGoalId 메서드는") + inner class GetValidPomodoroByDailyGoalId { @Test @DisplayName("뽀모도로가 존재하지 않으면 예외가 발생한다.") fun shouldThrowExceptionWhenPomodoroDoesNotExist() { @@ -60,7 +60,7 @@ class PomodoroQueryServiceTest : BaseUnitTest() { // when val exception = assertThrows { - pomodoroQueryService.getValidPomodoroByDailyGoal(dailyGoalId) + pomodoroQueryService.getValidPomodoroByDailyGoalId(dailyGoalId) } // then @@ -78,7 +78,7 @@ class PomodoroQueryServiceTest : BaseUnitTest() { // when val exception = assertThrows { - pomodoroQueryService.getValidPomodoroByDailyGoal(dailyGoalId) + pomodoroQueryService.getValidPomodoroByDailyGoalId(dailyGoalId) } // then @@ -93,7 +93,7 @@ class PomodoroQueryServiceTest : BaseUnitTest() { given(pomodoroRepository.findByDailyGoalId(dailyGoalId)).willReturn(Optional.of(pomodoro)) // when - val result = pomodoroQueryService.getValidPomodoroByDailyGoal(dailyGoalId) + val result = pomodoroQueryService.getValidPomodoroByDailyGoalId(dailyGoalId) // then assertThat(result).isEqualTo(pomodoro) diff --git a/src/test/kotlin/com/ject/studytrip/pomodoro/fixture/PomodoroFixture.kt b/src/test/kotlin/com/ject/studytrip/pomodoro/fixture/PomodoroFixture.kt index 23cba8a..91e2bba 100644 --- a/src/test/kotlin/com/ject/studytrip/pomodoro/fixture/PomodoroFixture.kt +++ b/src/test/kotlin/com/ject/studytrip/pomodoro/fixture/PomodoroFixture.kt @@ -12,13 +12,7 @@ class PomodoroFixture( var focusSessionCount: Int = 1 var breakDurationSeconds: Int = 0 - fun create(): Pomodoro = - PomodoroFactory.create( - dailyGoal, - focusDurationSeconds, - focusSessionCount, - breakDurationSeconds, - ) + fun create(): Pomodoro = PomodoroFactory.create(dailyGoal, focusDurationSeconds, focusSessionCount, breakDurationSeconds) fun createWithId(id: Long): Pomodoro = create().also { diff --git a/src/test/kotlin/com/ject/studytrip/pomodoro/helper/PomodoroTestHelper.kt b/src/test/kotlin/com/ject/studytrip/pomodoro/helper/PomodoroTestHelper.kt index 2461960..d9b4dac 100644 --- a/src/test/kotlin/com/ject/studytrip/pomodoro/helper/PomodoroTestHelper.kt +++ b/src/test/kotlin/com/ject/studytrip/pomodoro/helper/PomodoroTestHelper.kt @@ -10,16 +10,8 @@ import org.springframework.stereotype.Component class PomodoroTestHelper( private val pomodoroRepository: PomodoroRepository, ) { - fun savePomodoro(dailyGoal: DailyGoal): Pomodoro { - val pomodoro = PomodoroFixture(dailyGoal).create() - return pomodoroRepository.save(pomodoro) - } + fun savePomodoro(dailyGoal: DailyGoal): Pomodoro = pomodoroRepository.save(PomodoroFixture(dailyGoal).create()) - fun saveDeletedPomodoro(dailyGoal: DailyGoal): Pomodoro { - val pomodoro = - PomodoroFixture(dailyGoal).create().also { - it.updateDeletedAt() - } - return pomodoroRepository.save(pomodoro) - } + fun saveDeletedPomodoro(dailyGoal: DailyGoal): Pomodoro = + pomodoroRepository.save(PomodoroFixture(dailyGoal).create().also { it.updateDeletedAt() }) } diff --git a/src/test/kotlin/com/ject/studytrip/studylog/application/service/StudyLogCommandServiceTest.kt b/src/test/kotlin/com/ject/studytrip/studylog/application/service/StudyLogCommandServiceTest.kt index 0f527c5..d5de4fc 100644 --- a/src/test/kotlin/com/ject/studytrip/studylog/application/service/StudyLogCommandServiceTest.kt +++ b/src/test/kotlin/com/ject/studytrip/studylog/application/service/StudyLogCommandServiceTest.kt @@ -115,7 +115,7 @@ class StudyLogCommandServiceTest : BaseUnitTest() { @DisplayName("hardDeleteStudyLogs 메서드는") inner class HardDeleteStudyLogs { @Test - @DisplayName("삭제된 학습 로그가 존재하지 않으면 0을 반환한다.") + @DisplayName("삭제된 학습 로그가 하나라도 존재하지 않으면 0을 반환한다.") fun shouldReturnZeroWhenDeletedStudyLogsDoNotExist() { // given given(studyLogCommandRepository.deleteAllByDeletedAtIsNotNull()).willReturn(0L) @@ -128,7 +128,7 @@ class StudyLogCommandServiceTest : BaseUnitTest() { } @Test - @DisplayName("삭제된 학습 로그가 존재하면 해당 개수를 반환한다.") + @DisplayName("삭제된 학습 로그가 하나라도 존재하면 해당 개수를 반환한다.") fun shouldReturnCountWhenDeletedStudyLogsExist() { // given given(studyLogCommandRepository.deleteAllByDeletedAtIsNotNull()).willReturn(5L) @@ -145,7 +145,7 @@ class StudyLogCommandServiceTest : BaseUnitTest() { @DisplayName("hardDeleteStudyLogsOwnedByDeletedMember 메서드는") inner class HardDeleteStudyLogsOwnedByDeletedMember { @Test - @DisplayName("삭제된 멤버가 소유한 학습 로그가 존재하지 않으면 0을 반환한다.") + @DisplayName("삭제된 멤버가 소유한 학습 로그가 하나라도 존재하지 않으면 0을 반환한다.") fun shouldReturnZeroWhenStudyLogsOwnedByDeletedMemberDoNotExist() { // given given(studyLogCommandRepository.deleteAllByDeletedMemberOwner()).willReturn(0L) @@ -158,7 +158,7 @@ class StudyLogCommandServiceTest : BaseUnitTest() { } @Test - @DisplayName("삭제된 멤버가 소유한 학습 로그가 존재하면 해당 개수를 반환한다.") + @DisplayName("삭제된 멤버가 소유한 학습 로그가 하나라도 존재하면 해당 개수를 반환한다.") fun shouldReturnCountWhenStudyLogsOwnedByDeletedMemberExist() { // given given(studyLogCommandRepository.deleteAllByDeletedMemberOwner()).willReturn(5L) @@ -175,7 +175,7 @@ class StudyLogCommandServiceTest : BaseUnitTest() { @DisplayName("hardDeleteStudyLogsOwnedByDeletedDailyGoal 메서드는") inner class HardDeleteStudyLogsOwnedByDeletedDailyGoal { @Test - @DisplayName("삭제된 데일리 목표가 소유한 학습 로그가 존재하지 않으면 0을 반환한다.") + @DisplayName("삭제된 데일리 목표가 소유한 학습 로그가 하나라도 존재하지 않으면 0을 반환한다.") fun shouldReturnZeroWhenStudyLogsOwnedByDeletedDailyGoalDoNotExist() { // given given(studyLogCommandRepository.deleteAllByDeletedDailyGoalOwner()).willReturn(0L) @@ -188,7 +188,7 @@ class StudyLogCommandServiceTest : BaseUnitTest() { } @Test - @DisplayName("삭제된 데일리 목표가 소유한 학습 로그가 존재하면 해당 개수를 반환한다.") + @DisplayName("삭제된 데일리 목표가 소유한 학습 로그가 하나라도 존재하면 해당 개수를 반환한다.") fun shouldReturnCountWhenStudyLogsOwnedByDeletedDailyGoalExist() { // given given(studyLogCommandRepository.deleteAllByDeletedDailyGoalOwner()).willReturn(5L) @@ -202,31 +202,31 @@ class StudyLogCommandServiceTest : BaseUnitTest() { } @Nested - @DisplayName("hardDeleteStudyLogsByMember 메서드는") - inner class HardDeleteStudyLogsByMember { + @DisplayName("hardDeleteStudyLogsOwnedByMember 메서드는") + inner class HardDeleteStudyLogsOwnedByMember { @Test - @DisplayName("특정 멤버가 소유한 학습 로그가 존재하지 않으면 0을 반환한다.") + @DisplayName("특정 멤버가 소유한 학습 로그가 하나라도 존재하지 않으면 0을 반환한다.") fun shouldReturnZeroWhenStudyLogsOwnedByMemberDoNotExist() { // given val memberId = member.id given(studyLogCommandRepository.deleteByMemberId(memberId)).willReturn(0L) // when - val result = studyLogCommandService.hardDeleteStudyLogsByMember(memberId) + val result = studyLogCommandService.hardDeleteStudyLogsOwnedByMember(memberId) // then assertThat(result).isEqualTo(0L) } @Test - @DisplayName("특정 멤버가 소유한 학습 로그가 존재하면 해당 개수를 반환한다.") + @DisplayName("특정 멤버가 소유한 학습 로그가 하나라도 존재하면 해당 개수를 반환한다.") fun shouldReturnCountWhenStudyLogsOwnedByMemberExist() { // given val memberId = member.id given(studyLogCommandRepository.deleteByMemberId(memberId)).willReturn(5L) // when - val result = studyLogCommandService.hardDeleteStudyLogsByMember(memberId) + val result = studyLogCommandService.hardDeleteStudyLogsOwnedByMember(memberId) // then assertThat(result).isEqualTo(5L) diff --git a/src/test/kotlin/com/ject/studytrip/studylog/application/service/StudyLogDailyMissionCommandServiceTest.kt b/src/test/kotlin/com/ject/studytrip/studylog/application/service/StudyLogDailyMissionCommandServiceTest.kt new file mode 100644 index 0000000..d47df70 --- /dev/null +++ b/src/test/kotlin/com/ject/studytrip/studylog/application/service/StudyLogDailyMissionCommandServiceTest.kt @@ -0,0 +1,201 @@ +package com.ject.studytrip.studylog.application.service + +import com.ject.studytrip.BaseUnitTest +import com.ject.studytrip.member.domain.model.Member +import com.ject.studytrip.member.fixture.MemberFixture +import com.ject.studytrip.mission.domain.model.DailyMission +import com.ject.studytrip.mission.domain.model.Mission +import com.ject.studytrip.mission.fixture.DailyMissionFixture +import com.ject.studytrip.mission.fixture.MissionFixture +import com.ject.studytrip.stamp.fixture.StampFixture +import com.ject.studytrip.studylog.domain.model.StudyLog +import com.ject.studytrip.studylog.domain.repository.StudyLogDailyMissionCommandRepository +import com.ject.studytrip.studylog.domain.repository.StudyLogDailyMissionRepository +import com.ject.studytrip.studylog.fixture.StudyLogFixture +import com.ject.studytrip.trip.domain.model.DailyGoal +import com.ject.studytrip.trip.domain.model.TripCategory +import com.ject.studytrip.trip.fixture.DailyGoalFixture +import com.ject.studytrip.trip.fixture.TripFixture +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.mockito.ArgumentMatchers.anyList +import org.mockito.BDDMockito.given +import org.mockito.InjectMocks +import org.mockito.Mock + +@DisplayName("StudyLogDailyMissionCommandService 단위 테스트") +class StudyLogDailyMissionCommandServiceTest : BaseUnitTest() { + @InjectMocks + private lateinit var studyLogDailyMissionCommandService: StudyLogDailyMissionCommandService + + @Mock + private lateinit var studyLogDailyMissionRepository: StudyLogDailyMissionRepository + + @Mock + private lateinit var studyLogDailyMissionCommandRepository: StudyLogDailyMissionCommandRepository + + private lateinit var member: Member + private lateinit var mission1: Mission + private lateinit var mission2: Mission + private lateinit var dailyGoal: DailyGoal + private lateinit var dailyMission1: DailyMission + private lateinit var dailyMission2: DailyMission + private lateinit var studyLog: StudyLog + + @BeforeEach + fun setUp() { + member = MemberFixture.createMemberFromKakaoWithId(1L) + val trip = TripFixture.createTripWithId(1L, member, TripCategory.COURSE) + val stamp = StampFixture.createStampWithId(1L, trip, 1) + mission1 = MissionFixture(stamp).createWithId(1L) + mission2 = MissionFixture(stamp).createWithId(2L) + dailyGoal = DailyGoalFixture.createDailyGoalWithId(1L, trip) + dailyMission1 = DailyMissionFixture(mission1, dailyGoal).createWithId(1L) + dailyMission2 = DailyMissionFixture(mission2, dailyGoal).createWithId(2L) + studyLog = StudyLogFixture(member, dailyGoal).createWithId(1L) + } + + @Nested + @DisplayName("createStudyLogDailyMissions 메서드는") + inner class CreateStudyLogDailyMissions { + @Test + @DisplayName("학습 로그와 데일리 미션 목록을 이용해 학습 로그 데일리 미션 목록을 생성하고 반환한다.") + fun shouldCreateAndReturnStudyLogDailyMissions() { + // given + val dailyMissions = listOf(dailyMission1, dailyMission2) + given(studyLogDailyMissionRepository.saveAll(anyList())) + .willAnswer { it.getArgument(0) } + + // when + val result = studyLogDailyMissionCommandService.createStudyLogDailyMissions(studyLog, dailyMissions) + + // then + assertThat(result).hasSize(dailyMissions.size) + } + } + + @Nested + @DisplayName("hardDeleteStudyLogDailyMissions 메서드는") + inner class HardDeleteStudyLogDailyMissions { + @Test + @DisplayName("삭제된 학습 로그 데일리 미션이 하나라도 존재하지 않으면 0을 반환한다.") + fun shouldReturnZeroWhenDeletedStudyLogDailyMissionsDoNotExist() { + // given + given(studyLogDailyMissionCommandRepository.deleteAllByDeletedAtIsNotNull()).willReturn(0L) + + // when + val result = studyLogDailyMissionCommandService.hardDeleteStudyLogDailyMissions() + + // then + assertThat(result).isEqualTo(0L) + } + + @Test + @DisplayName("삭제된 학습 로그 데일리 미션이 하나라도 존재하면 해당 개수를 반환한다.") + fun shouldReturnCountWhenDeletedStudyLogDailyMissionsExist() { + // given + given(studyLogDailyMissionCommandRepository.deleteAllByDeletedAtIsNotNull()).willReturn(5L) + + // when + val result = studyLogDailyMissionCommandService.hardDeleteStudyLogDailyMissions() + + // then + assertThat(result).isEqualTo(5L) + } + } + + @Nested + @DisplayName("hardDeleteStudyLogDailyMissionsOwnedByDeletedDailyMission 메서드는") + inner class HardDeleteStudyLogDailyMissionsOwnedByDeletedDailyMission { + @Test + @DisplayName("삭제된 데일리 미션이 소유한 학습 로그 데일리 미션이 하나라도 존재하지 않으면 0을 반환한다.") + fun shouldReturnZeroWhenStudyLogDailyMissionsOwnedByDeletedDailyMissionDoNotExist() { + // given + given(studyLogDailyMissionCommandRepository.deleteAllByDeletedDailyMissionOwner()).willReturn(0L) + + // when + val result = studyLogDailyMissionCommandService.hardDeleteStudyLogDailyMissionsOwnedByDeletedDailyMission() + + // then + assertThat(result).isEqualTo(0L) + } + + @Test + @DisplayName("삭제된 데일리 미션이 소유한 학습 로그 데일리 미션이 하나라도 존재하면 해당 개수를 반환한다.") + fun shouldReturnCountWhenStudyLogDailyMissionsOwnedByDeletedDailyMissionExist() { + // given + given(studyLogDailyMissionCommandRepository.deleteAllByDeletedDailyMissionOwner()).willReturn(5L) + + // when + val result = studyLogDailyMissionCommandService.hardDeleteStudyLogDailyMissionsOwnedByDeletedDailyMission() + + // then + assertThat(result).isEqualTo(5L) + } + } + + @Nested + @DisplayName("hardDeleteStudyLogDailyMissionsOwnedByDeletedStudyLog 메서드는") + inner class HardDeleteStudyLogDailyMissionsOwnedByDeletedStudyLog { + @Test + @DisplayName("삭제된 학습 로그가 소유한 학습 로그 데일리 미션이 하나라도 존재하지 않으면 0을 반환한다.") + fun shouldReturnZeroWhenStudyLogDailyMissionsOwnedByDeletedStudyLogDoNotExist() { + // given + given(studyLogDailyMissionCommandRepository.deleteAllByDeletedStudyLogOwner()).willReturn(0L) + + // when + val result = studyLogDailyMissionCommandService.hardDeleteStudyLogDailyMissionsOwnedByDeletedStudyLog() + + // then + assertThat(result).isEqualTo(0L) + } + + @Test + @DisplayName("삭제된 학습 로그가 소유한 학습 로그 데일리 미션이 하나라도 존재하면 해당 개수를 반환한다.") + fun shouldReturnCountWhenStudyLogDailyMissionsOwnedByDeletedStudyLogExist() { + // given + given(studyLogDailyMissionCommandRepository.deleteAllByDeletedStudyLogOwner()).willReturn(5L) + + // when + val result = studyLogDailyMissionCommandService.hardDeleteStudyLogDailyMissionsOwnedByDeletedStudyLog() + + // then + assertThat(result).isEqualTo(5L) + } + } + + @Nested + @DisplayName("hardDeleteStudyLogDailyMissionsOwnedByMember 메서드는") + inner class HardDeleteStudyLogDailyMissionsOwnedByMember { + @Test + @DisplayName("특정 멤버가 소유한 학습 로그 데일리 미션이 하나라도 존재하지 않으면 0을 반환한다.") + fun shouldReturnZeroWhenStudyLogDailyMissionsOwnedByMemberDoNotExist() { + // given + val memberId = member.id + given(studyLogDailyMissionCommandRepository.deleteAllByMemberId(memberId)).willReturn(0L) + + // when + val result = studyLogDailyMissionCommandService.hardDeleteStudyLogDailyMissionsOwnedByMember(memberId) + + // then + assertThat(result).isEqualTo(0L) + } + + @Test + @DisplayName("특정 멤버가 소유한 학습 로그 데일리 미션이 하나라도 존재하면 해당 개수를 반환한다.") + fun shouldReturnCountWhenStudyLogDailyMissionsOwnedByMemberExist() { + // given + val memberId = member.id + given(studyLogDailyMissionCommandRepository.deleteAllByMemberId(memberId)).willReturn(5L) + + // when + val result = studyLogDailyMissionCommandService.hardDeleteStudyLogDailyMissionsOwnedByMember(memberId) + + // then + assertThat(result).isEqualTo(5L) + } + } +} diff --git a/src/test/kotlin/com/ject/studytrip/studylog/application/service/StudyLogDailyMissionQueryServiceTest.kt b/src/test/kotlin/com/ject/studytrip/studylog/application/service/StudyLogDailyMissionQueryServiceTest.kt new file mode 100644 index 0000000..34f2aab --- /dev/null +++ b/src/test/kotlin/com/ject/studytrip/studylog/application/service/StudyLogDailyMissionQueryServiceTest.kt @@ -0,0 +1,80 @@ +package com.ject.studytrip.studylog.application.service + +import com.ject.studytrip.BaseUnitTest +import com.ject.studytrip.member.fixture.MemberFixture +import com.ject.studytrip.mission.fixture.DailyMissionFixture +import com.ject.studytrip.mission.fixture.MissionFixture +import com.ject.studytrip.stamp.fixture.StampFixture +import com.ject.studytrip.studylog.domain.model.StudyLog +import com.ject.studytrip.studylog.domain.model.StudyLogDailyMission +import com.ject.studytrip.studylog.domain.repository.StudyLogDailyMissionQueryRepository +import com.ject.studytrip.studylog.fixture.StudyLogDailyMissionFixture +import com.ject.studytrip.studylog.fixture.StudyLogFixture +import com.ject.studytrip.trip.domain.model.TripCategory +import com.ject.studytrip.trip.fixture.DailyGoalFixture +import com.ject.studytrip.trip.fixture.TripFixture +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.mockito.BDDMockito.given +import org.mockito.InjectMocks +import org.mockito.Mock + +@DisplayName("StudyLogDailyMissionQueryService 단위 테스트") +class StudyLogDailyMissionQueryServiceTest : BaseUnitTest() { + @InjectMocks + private lateinit var studyLogDailyMissionQueryService: StudyLogDailyMissionQueryService + + @Mock + private lateinit var studyLogDailyMissionQueryRepository: StudyLogDailyMissionQueryRepository + + private lateinit var studyLog1: StudyLog + private lateinit var studyLog2: StudyLog + private lateinit var studyLogDailyMission1: StudyLogDailyMission + private lateinit var studyLogDailyMission2: StudyLogDailyMission + private lateinit var studyLogDailyMission3: StudyLogDailyMission + + @BeforeEach + fun setUp() { + val member = MemberFixture.createMemberFromKakaoWithId(1L) + val trip = TripFixture.createTripWithId(1L, member, TripCategory.COURSE) + val stamp = StampFixture.createStampWithId(1L, trip, 1) + val mission = MissionFixture(stamp).createWithId(1L) + val dailyGoal = DailyGoalFixture.createDailyGoalWithId(1L, trip) + val dailyMission1 = DailyMissionFixture(mission, dailyGoal).createWithId(1L) + val dailyMission2 = DailyMissionFixture(mission, dailyGoal).createWithId(2L) + studyLog1 = StudyLogFixture(member, dailyGoal).createWithId(1L) + studyLog2 = StudyLogFixture(member, dailyGoal).createWithId(2L) + studyLogDailyMission1 = StudyLogDailyMissionFixture(studyLog1, dailyMission1).createWithId(1L) + studyLogDailyMission2 = StudyLogDailyMissionFixture(studyLog1, dailyMission1).createWithId(2L) + studyLogDailyMission3 = StudyLogDailyMissionFixture(studyLog2, dailyMission2).createWithId(3L) + } + + @Nested + @DisplayName("getGroupedStudyLogDailyMissionsByStudyLogIds 메서드는") + inner class GetGroupedStudyLogDailyMissionsByStudyLogIds { + @Test + @DisplayName("학습 로그 ID 목록으로 그룹화된 StudyLogDailyMission Map을 반환한다.") + fun shouldReturnGroupedStudyLogDailyMissionMapByStudyLogIds() { + // given + val studyLogId1 = studyLog1.id + val studyLogId2 = studyLog2.id + val studyLogIds = listOf(studyLogId1, studyLogId2) + val studyLogDailyMissionMap = + mapOf( + studyLogId1 to listOf(studyLogDailyMission1, studyLogDailyMission2), + studyLogId2 to listOf(studyLogDailyMission3), + ) + given(studyLogDailyMissionQueryRepository.findStudyLogDailyMissionsGroupedByStudyLogId(studyLogIds)) + .willReturn(studyLogDailyMissionMap) + + // when + val result = studyLogDailyMissionQueryService.getGroupedStudyLogDailyMissionsByStudyLogIds(studyLogIds) + + // then + assertThat(result).isEqualTo(studyLogDailyMissionMap) + } + } +} diff --git a/src/test/kotlin/com/ject/studytrip/studylog/application/service/StudyLogQueryServiceTest.kt b/src/test/kotlin/com/ject/studytrip/studylog/application/service/StudyLogQueryServiceTest.kt index 2d878af..0574914 100644 --- a/src/test/kotlin/com/ject/studytrip/studylog/application/service/StudyLogQueryServiceTest.kt +++ b/src/test/kotlin/com/ject/studytrip/studylog/application/service/StudyLogQueryServiceTest.kt @@ -48,13 +48,13 @@ class StudyLogQueryServiceTest : BaseUnitTest() { private lateinit var studyLog2: StudyLog private lateinit var tripReport: TripReport + private val pageable: Pageable = PageRequest.of(DEFAULT_PAGE, DEFAULT_SIZE) + companion object { private const val DEFAULT_PAGE = 0 private const val DEFAULT_SIZE = 5 } - private val pageable: Pageable = PageRequest.of(DEFAULT_PAGE, DEFAULT_SIZE) - @BeforeEach fun setUp() { member = MemberFixture.createMemberFromKakaoWithId(1L) @@ -115,8 +115,7 @@ class StudyLogQueryServiceTest : BaseUnitTest() { // then assertThat(result.content).hasSize(studyLogs.size) - assertThat(result.content[0]).isEqualTo(studyLog1) - assertThat(result.content[1]).isEqualTo(studyLog2) + assertThat(result).containsExactly(studyLog1, studyLog2) } @Test @@ -134,8 +133,7 @@ class StudyLogQueryServiceTest : BaseUnitTest() { // then assertThat(result.content).hasSize(studyLogs.size) - assertThat(result.content[0]).isEqualTo(studyLog2) - assertThat(result.content[1]).isEqualTo(studyLog1) + assertThat(result).containsExactly(studyLog2, studyLog1) } } @@ -250,8 +248,7 @@ class StudyLogQueryServiceTest : BaseUnitTest() { // then assertThat(result.content).hasSize(studyLogs.size) - assertThat(result.content[0]).isEqualTo(studyLog1) - assertThat(result.content[1]).isEqualTo(studyLog2) + assertThat(result).containsExactly(studyLog1, studyLog2) } } @@ -277,7 +274,9 @@ class StudyLogQueryServiceTest : BaseUnitTest() { fun shouldReturnStudyLogIdsWhenStudyLogExists() { // given val tripId = courseTrip.id - val studyLogIds = listOf(studyLog1.id, studyLog2.id) + val studyLogId1 = studyLog1.id + val studyLogId2 = studyLog2.id + val studyLogIds = listOf(studyLogId1, studyLogId2) given(studyLogQueryRepository.findAllIdsByTripIdOrderByCreatedDesc(tripId)).willReturn(studyLogIds) // when @@ -285,8 +284,7 @@ class StudyLogQueryServiceTest : BaseUnitTest() { // then assertThat(result).hasSize(studyLogIds.size) - assertThat(result[0]).isEqualTo(studyLog1.id) - assertThat(result[1]).isEqualTo(studyLog2.id) + assertThat(result).containsExactly(studyLogId1, studyLogId2) } } diff --git a/src/test/kotlin/com/ject/studytrip/studylog/fixture/StudyLogDailyMissionFixture.kt b/src/test/kotlin/com/ject/studytrip/studylog/fixture/StudyLogDailyMissionFixture.kt new file mode 100644 index 0000000..ac9a31a --- /dev/null +++ b/src/test/kotlin/com/ject/studytrip/studylog/fixture/StudyLogDailyMissionFixture.kt @@ -0,0 +1,19 @@ +package com.ject.studytrip.studylog.fixture + +import com.ject.studytrip.mission.domain.model.DailyMission +import com.ject.studytrip.studylog.domain.factory.StudyLogDailyMissionFactory +import com.ject.studytrip.studylog.domain.model.StudyLog +import com.ject.studytrip.studylog.domain.model.StudyLogDailyMission +import org.springframework.test.util.ReflectionTestUtils + +class StudyLogDailyMissionFixture( + private val studyLog: StudyLog, + private val dailyMission: DailyMission, +) { + fun create(): StudyLogDailyMission = StudyLogDailyMissionFactory.create(studyLog, dailyMission) + + fun createWithId(id: Long): StudyLogDailyMission = + create().also { + ReflectionTestUtils.setField(it, "id", id) + } +} diff --git a/src/test/kotlin/com/ject/studytrip/studylog/helper/StudyLogDailyMissionTestHelper.kt b/src/test/kotlin/com/ject/studytrip/studylog/helper/StudyLogDailyMissionTestHelper.kt new file mode 100644 index 0000000..f657abb --- /dev/null +++ b/src/test/kotlin/com/ject/studytrip/studylog/helper/StudyLogDailyMissionTestHelper.kt @@ -0,0 +1,19 @@ +package com.ject.studytrip.studylog.helper + +import com.ject.studytrip.mission.domain.model.DailyMission +import com.ject.studytrip.studylog.domain.model.StudyLog +import com.ject.studytrip.studylog.domain.model.StudyLogDailyMission +import com.ject.studytrip.studylog.domain.repository.StudyLogDailyMissionRepository +import com.ject.studytrip.studylog.fixture.StudyLogDailyMissionFixture +import org.springframework.stereotype.Component + +@Component +class StudyLogDailyMissionTestHelper( + private val studyLogDailyMissionRepository: StudyLogDailyMissionRepository, +) { + fun saveStudyLogDailyMissions( + studyLog: StudyLog, + dailyMission: DailyMission, + ): List = + studyLogDailyMissionRepository.saveAll(listOf(StudyLogDailyMissionFixture(studyLog, dailyMission).create())) +} diff --git a/src/test/kotlin/com/ject/studytrip/studylog/presentation/controller/StudyLogControllerIntegrationTest.kt b/src/test/kotlin/com/ject/studytrip/studylog/presentation/controller/StudyLogControllerIntegrationTest.kt index 23ed59a..5066064 100644 --- a/src/test/kotlin/com/ject/studytrip/studylog/presentation/controller/StudyLogControllerIntegrationTest.kt +++ b/src/test/kotlin/com/ject/studytrip/studylog/presentation/controller/StudyLogControllerIntegrationTest.kt @@ -359,8 +359,8 @@ class StudyLogControllerIntegrationTest : BaseIntegrationTest() { resultActions .andExpect(status().isForbidden) .andExpect(jsonPath("$.success").value(false)) - .andExpect(jsonPath("$.status").value(DailyMissionErrorCode.DAILY_MISSION_NOT_BELONG_TO_DAILY_GOAL.status.value())) - .andExpect(jsonPath("$.data.message").value(DailyMissionErrorCode.DAILY_MISSION_NOT_BELONG_TO_DAILY_GOAL.message)) + .andExpect(jsonPath("$.status").value(DailyMissionErrorCode.DAILY_MISSION_NOT_BELONGS_TO_DAILY_GOAL.status.value())) + .andExpect(jsonPath("$.data.message").value(DailyMissionErrorCode.DAILY_MISSION_NOT_BELONGS_TO_DAILY_GOAL.message)) } @Test @@ -460,7 +460,7 @@ class StudyLogControllerIntegrationTest : BaseIntegrationTest() { @Test @DisplayName("유효한 요청이 들어오면 학습 로그를 생성하고 반환한다.") - fun shouldReturnStudyLogWhenRequestIsValid() { + fun shouldCreateAndReturnStudyLogWhenRequestIsValid() { // given val request = fixture.withSelectedDailyMissionIds(listOf(dailyMission.id)).build() val initialCompletedMissions = stamp.completedMissions @@ -722,7 +722,7 @@ class StudyLogControllerIntegrationTest : BaseIntegrationTest() { private fun getResultActions( token: String, - studyLogId: Long, + studyLogId: Any, request: PresignStudyLogImageRequest, ): ResultActions = mockMvc.perform( @@ -749,6 +749,24 @@ class StudyLogControllerIntegrationTest : BaseIntegrationTest() { .andExpect(jsonPath("$.data.message").value(AuthErrorCode.UNAUTHENTICATED.message)) } + @Test + @DisplayName("PathVariable 학습 로그 ID 타입이 올바르지 않으면 400 Bad Request를 반환한다.") + fun shouldReturnBadRequestWhenStudyLogIdTypeMismatch() { + // given + val studyLogId = "abc" + val request = fixture.build() + + // when + val resultActions = getResultActions(token, studyLogId, request) + + // then + resultActions + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(CommonErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH.status.value())) + .andExpect(jsonPath("$.data.message").value(CommonErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH.message)) + } + @Test @DisplayName("파일명이 비어있으면 400 Bad Request를 반환한다.") fun shouldReturnBadRequestWhenFilenameIsEmpty() { @@ -814,7 +832,7 @@ class StudyLogControllerIntegrationTest : BaseIntegrationTest() { private fun getResultActions( token: String, - studyLogId: Long, + studyLogId: Any, request: ConfirmStudyLogImageRequest, ): ResultActions = mockMvc.perform( @@ -841,6 +859,24 @@ class StudyLogControllerIntegrationTest : BaseIntegrationTest() { .andExpect(jsonPath("$.data.message").value(AuthErrorCode.UNAUTHENTICATED.message)) } + @Test + @DisplayName("PathVariable 학습 로그 ID 타입이 올바르지 않으면 400 Bad Request를 반환한다.") + fun shouldReturnBadRequestWhenStudyLogIdTypeMismatch() { + // given + val studyLogId = "abc" + val request = fixture.build() + + // when + val resultActions = getResultActions(token, studyLogId, request) + + // then + resultActions + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.status").value(CommonErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH.status.value())) + .andExpect(jsonPath("$.data.message").value(CommonErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH.message)) + } + @Test @DisplayName("tmpKey가 비어있으면 400 Bad Request를 반환한다.") fun shouldReturnBadRequestWhenTmpKeyIsEmpty() {