diff --git a/src/main/java/com/ject/studytrip/dummy/application/dto/CreateDummyMissionCommand.java b/src/main/java/com/ject/studytrip/dummy/application/dto/CreateDummyMissionCommand.java new file mode 100644 index 0000000..f539f01 --- /dev/null +++ b/src/main/java/com/ject/studytrip/dummy/application/dto/CreateDummyMissionCommand.java @@ -0,0 +1,7 @@ +package com.ject.studytrip.dummy.application.dto; + +public record CreateDummyMissionCommand(String name) { + public static CreateDummyMissionCommand of(String name) { + return new CreateDummyMissionCommand(name); + } +} diff --git a/src/main/java/com/ject/studytrip/dummy/application/dto/CreateDummyStampCommand.java b/src/main/java/com/ject/studytrip/dummy/application/dto/CreateDummyStampCommand.java new file mode 100644 index 0000000..9a998d9 --- /dev/null +++ b/src/main/java/com/ject/studytrip/dummy/application/dto/CreateDummyStampCommand.java @@ -0,0 +1,9 @@ +package com.ject.studytrip.dummy.application.dto; + +import java.time.LocalDate; + +public record CreateDummyStampCommand(String name, int stampOrder, LocalDate endDate) { + public static CreateDummyStampCommand of(String name, int stampOrder, LocalDate endDate) { + return new CreateDummyStampCommand(name, stampOrder, endDate); + } +} diff --git a/src/main/java/com/ject/studytrip/dummy/application/dto/CreateDummyTripCommand.java b/src/main/java/com/ject/studytrip/dummy/application/dto/CreateDummyTripCommand.java new file mode 100644 index 0000000..93e269b --- /dev/null +++ b/src/main/java/com/ject/studytrip/dummy/application/dto/CreateDummyTripCommand.java @@ -0,0 +1,12 @@ +package com.ject.studytrip.dummy.application.dto; + +import com.ject.studytrip.trip.domain.model.TripCategory; +import java.time.LocalDate; + +public record CreateDummyTripCommand( + String name, String memo, TripCategory tripCategory, LocalDate endDate) { + public static CreateDummyTripCommand of( + String name, String memo, TripCategory tripCategory, LocalDate endDate) { + return new CreateDummyTripCommand(name, memo, tripCategory, endDate); + } +} diff --git a/src/main/java/com/ject/studytrip/dummy/application/dto/DummyMissionInfo.java b/src/main/java/com/ject/studytrip/dummy/application/dto/DummyMissionInfo.java new file mode 100644 index 0000000..13463dd --- /dev/null +++ b/src/main/java/com/ject/studytrip/dummy/application/dto/DummyMissionInfo.java @@ -0,0 +1,22 @@ +package com.ject.studytrip.dummy.application.dto; + +import com.ject.studytrip.global.util.DateUtil; +import com.ject.studytrip.mission.domain.model.Mission; + +public record DummyMissionInfo( + Long missionId, + String missionName, + boolean completed, + String createdAt, + String updatedAt, + String deletedAt) { + public static DummyMissionInfo from(Mission mission) { + return new DummyMissionInfo( + 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/dummy/application/dto/DummyMissionsInfo.java b/src/main/java/com/ject/studytrip/dummy/application/dto/DummyMissionsInfo.java new file mode 100644 index 0000000..86fee60 --- /dev/null +++ b/src/main/java/com/ject/studytrip/dummy/application/dto/DummyMissionsInfo.java @@ -0,0 +1,9 @@ +package com.ject.studytrip.dummy.application.dto; + +import java.util.List; + +public record DummyMissionsInfo(List missionInfos) { + public static DummyMissionsInfo of(List missionInfos) { + return new DummyMissionsInfo(missionInfos); + } +} diff --git a/src/main/java/com/ject/studytrip/dummy/application/dto/DummyStampInfo.java b/src/main/java/com/ject/studytrip/dummy/application/dto/DummyStampInfo.java new file mode 100644 index 0000000..e0219dc --- /dev/null +++ b/src/main/java/com/ject/studytrip/dummy/application/dto/DummyStampInfo.java @@ -0,0 +1,30 @@ +package com.ject.studytrip.dummy.application.dto; + +import com.ject.studytrip.global.util.DateUtil; +import com.ject.studytrip.stamp.domain.model.Stamp; + +public record DummyStampInfo( + Long stampId, + String stampName, + int stampOrder, + String endDate, + int totalMissions, + int completedMissions, + boolean completed, + String createdAt, + String updatedAt, + String deletedAt) { + public static DummyStampInfo from(Stamp stamp) { + return new DummyStampInfo( + stamp.getId(), + stamp.getName(), + stamp.getStampOrder(), + DateUtil.formatDate(stamp.getEndDate()), + stamp.getTotalMissions(), + stamp.getCompletedMissions(), + stamp.isCompleted(), + DateUtil.formatDateTime(stamp.getCreatedAt()), + DateUtil.formatDateTime(stamp.getUpdatedAt()), + DateUtil.formatDateTime(stamp.getDeletedAt())); + } +} diff --git a/src/main/java/com/ject/studytrip/dummy/application/dto/DummyStampsInfo.java b/src/main/java/com/ject/studytrip/dummy/application/dto/DummyStampsInfo.java new file mode 100644 index 0000000..0da4af9 --- /dev/null +++ b/src/main/java/com/ject/studytrip/dummy/application/dto/DummyStampsInfo.java @@ -0,0 +1,9 @@ +package com.ject.studytrip.dummy.application.dto; + +import java.util.List; + +public record DummyStampsInfo(List stampsInfos) { + public static DummyStampsInfo of(List stampsInfos) { + return new DummyStampsInfo(stampsInfos); + } +} diff --git a/src/main/java/com/ject/studytrip/dummy/application/facade/DummyMissionFacade.java b/src/main/java/com/ject/studytrip/dummy/application/facade/DummyMissionFacade.java new file mode 100644 index 0000000..4f423ad --- /dev/null +++ b/src/main/java/com/ject/studytrip/dummy/application/facade/DummyMissionFacade.java @@ -0,0 +1,39 @@ +package com.ject.studytrip.dummy.application.facade; + +import com.ject.studytrip.dummy.application.dto.DummyMissionInfo; +import com.ject.studytrip.dummy.application.dto.DummyMissionsInfo; +import com.ject.studytrip.dummy.application.service.DummyMissionCommandService; +import com.ject.studytrip.dummy.application.service.DummyStampCommandService; +import com.ject.studytrip.dummy.application.service.DummyTripCommandService; +import com.ject.studytrip.member.application.service.MemberQueryService; +import com.ject.studytrip.member.domain.model.Member; +import com.ject.studytrip.mission.domain.model.Mission; +import com.ject.studytrip.stamp.domain.model.Stamp; +import com.ject.studytrip.trip.domain.model.Trip; +import java.util.List; +import java.util.stream.Stream; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class DummyMissionFacade { + private final MemberQueryService memberQueryService; + + private final DummyTripCommandService dummyTripCommandService; + private final DummyStampCommandService dummyStampCommandService; + private final DummyMissionCommandService dummyMissionCommandService; + + public DummyMissionsInfo generateDummyMissions(Long memberId, String category, int count) { + Member member = memberQueryService.getValidMember(memberId); + + Trip trip = dummyTripCommandService.createDummyTrip(member, category, count); + Stamp stamp = dummyStampCommandService.createDummyStamp(trip, count); + List missions = + Stream.generate(() -> dummyMissionCommandService.createDummyMission(stamp)) + .limit(count) + .toList(); + + return DummyMissionsInfo.of(missions.stream().map(DummyMissionInfo::from).toList()); + } +} diff --git a/src/main/java/com/ject/studytrip/dummy/application/facade/DummyStampFacade.java b/src/main/java/com/ject/studytrip/dummy/application/facade/DummyStampFacade.java new file mode 100644 index 0000000..bc123ba --- /dev/null +++ b/src/main/java/com/ject/studytrip/dummy/application/facade/DummyStampFacade.java @@ -0,0 +1,35 @@ +package com.ject.studytrip.dummy.application.facade; + +import com.ject.studytrip.dummy.application.dto.DummyStampInfo; +import com.ject.studytrip.dummy.application.dto.DummyStampsInfo; +import com.ject.studytrip.dummy.application.service.DummyStampCommandService; +import com.ject.studytrip.dummy.application.service.DummyTripCommandService; +import com.ject.studytrip.member.application.service.MemberQueryService; +import com.ject.studytrip.member.domain.model.Member; +import com.ject.studytrip.stamp.domain.model.Stamp; +import com.ject.studytrip.trip.domain.model.Trip; +import java.util.List; +import java.util.stream.IntStream; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class DummyStampFacade { + private final MemberQueryService memberQueryService; + + private final DummyTripCommandService dummyTripCommandService; + private final DummyStampCommandService dummyStampCommandService; + + public DummyStampsInfo generateDummyStamps(Long memberId, String category, int count) { + Member member = memberQueryService.getValidMember(memberId); + + Trip trip = dummyTripCommandService.createDummyTrip(member, category, count); + List stamps = + IntStream.rangeClosed(1, count) + .mapToObj(order -> dummyStampCommandService.createDummyStamp(trip, order)) + .toList(); + + return DummyStampsInfo.of(stamps.stream().map(DummyStampInfo::from).toList()); + } +} diff --git a/src/main/java/com/ject/studytrip/dummy/application/generator/CreateDummyMissionCommandGenerator.java b/src/main/java/com/ject/studytrip/dummy/application/generator/CreateDummyMissionCommandGenerator.java new file mode 100644 index 0000000..7ae33fd --- /dev/null +++ b/src/main/java/com/ject/studytrip/dummy/application/generator/CreateDummyMissionCommandGenerator.java @@ -0,0 +1,11 @@ +package com.ject.studytrip.dummy.application.generator; + +import com.ject.studytrip.dummy.application.dto.CreateDummyMissionCommand; + +public final class CreateDummyMissionCommandGenerator { + private CreateDummyMissionCommandGenerator() {} + + public static CreateDummyMissionCommand of() { + return CreateDummyMissionCommand.of("testMission"); + } +} diff --git a/src/main/java/com/ject/studytrip/dummy/application/generator/CreateDummyStampCommandGenerator.java b/src/main/java/com/ject/studytrip/dummy/application/generator/CreateDummyStampCommandGenerator.java new file mode 100644 index 0000000..edc4af3 --- /dev/null +++ b/src/main/java/com/ject/studytrip/dummy/application/generator/CreateDummyStampCommandGenerator.java @@ -0,0 +1,24 @@ +package com.ject.studytrip.dummy.application.generator; + +import com.ject.studytrip.dummy.application.dto.CreateDummyStampCommand; +import com.ject.studytrip.trip.domain.model.TripCategory; +import java.time.LocalDate; + +public final class CreateDummyStampCommandGenerator { + private CreateDummyStampCommandGenerator() {} + + public static CreateDummyStampCommand of(TripCategory tripCategory, int stampOrder) { + return switch (tripCategory) { + case COURSE -> ofCourse(stampOrder); + case EXPLORE -> ofExplore(); + }; + } + + private static CreateDummyStampCommand ofCourse(int stampOrder) { + return CreateDummyStampCommand.of("testStamp", stampOrder, LocalDate.now().plusDays(10)); + } + + private static CreateDummyStampCommand ofExplore() { + return CreateDummyStampCommand.of("testStamp", 0, null); + } +} diff --git a/src/main/java/com/ject/studytrip/dummy/application/generator/CreateDummyTripCommandGenerator.java b/src/main/java/com/ject/studytrip/dummy/application/generator/CreateDummyTripCommandGenerator.java new file mode 100644 index 0000000..1a2e495 --- /dev/null +++ b/src/main/java/com/ject/studytrip/dummy/application/generator/CreateDummyTripCommandGenerator.java @@ -0,0 +1,25 @@ +package com.ject.studytrip.dummy.application.generator; + +import com.ject.studytrip.dummy.application.dto.CreateDummyTripCommand; +import com.ject.studytrip.trip.domain.model.TripCategory; +import java.time.LocalDate; + +public final class CreateDummyTripCommandGenerator { + private CreateDummyTripCommandGenerator() {} + + public static CreateDummyTripCommand of(TripCategory tripCategory) { + return switch (tripCategory) { + case COURSE -> ofCourse(); + case EXPLORE -> ofExplore(); + }; + } + + private static CreateDummyTripCommand ofCourse() { + return CreateDummyTripCommand.of( + "testTrip", "testMemo", TripCategory.COURSE, LocalDate.now().plusDays(10)); + } + + private static CreateDummyTripCommand ofExplore() { + return CreateDummyTripCommand.of("testTrip", "testMemo", TripCategory.EXPLORE, null); + } +} diff --git a/src/main/java/com/ject/studytrip/dummy/application/service/DummyMissionCommandService.java b/src/main/java/com/ject/studytrip/dummy/application/service/DummyMissionCommandService.java new file mode 100644 index 0000000..3d5108b --- /dev/null +++ b/src/main/java/com/ject/studytrip/dummy/application/service/DummyMissionCommandService.java @@ -0,0 +1,18 @@ +package com.ject.studytrip.dummy.application.service; + +import com.ject.studytrip.dummy.application.dto.CreateDummyMissionCommand; +import com.ject.studytrip.dummy.application.generator.CreateDummyMissionCommandGenerator; +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.stereotype.Service; + +@Service +public class DummyMissionCommandService { + + public Mission createDummyMission(Stamp stamp) { + CreateDummyMissionCommand command = CreateDummyMissionCommandGenerator.of(); + + return MissionFactory.create(stamp, command.name()); + } +} diff --git a/src/main/java/com/ject/studytrip/dummy/application/service/DummyStampCommandService.java b/src/main/java/com/ject/studytrip/dummy/application/service/DummyStampCommandService.java new file mode 100644 index 0000000..a6ed863 --- /dev/null +++ b/src/main/java/com/ject/studytrip/dummy/application/service/DummyStampCommandService.java @@ -0,0 +1,19 @@ +package com.ject.studytrip.dummy.application.service; + +import com.ject.studytrip.dummy.application.dto.CreateDummyStampCommand; +import com.ject.studytrip.dummy.application.generator.CreateDummyStampCommandGenerator; +import com.ject.studytrip.stamp.domain.factory.StampFactory; +import com.ject.studytrip.stamp.domain.model.Stamp; +import com.ject.studytrip.trip.domain.model.Trip; +import org.springframework.stereotype.Service; + +@Service +public class DummyStampCommandService { + + public Stamp createDummyStamp(Trip trip, int stampOrder) { + CreateDummyStampCommand command = + CreateDummyStampCommandGenerator.of(trip.getCategory(), stampOrder); + + return StampFactory.create(trip, command.name(), command.stampOrder(), command.endDate()); + } +} diff --git a/src/main/java/com/ject/studytrip/dummy/application/service/DummyTripCommandService.java b/src/main/java/com/ject/studytrip/dummy/application/service/DummyTripCommandService.java new file mode 100644 index 0000000..98a7aad --- /dev/null +++ b/src/main/java/com/ject/studytrip/dummy/application/service/DummyTripCommandService.java @@ -0,0 +1,26 @@ +package com.ject.studytrip.dummy.application.service; + +import com.ject.studytrip.dummy.application.dto.CreateDummyTripCommand; +import com.ject.studytrip.dummy.application.generator.CreateDummyTripCommandGenerator; +import com.ject.studytrip.member.domain.model.Member; +import com.ject.studytrip.trip.domain.factory.TripFactory; +import com.ject.studytrip.trip.domain.model.Trip; +import com.ject.studytrip.trip.domain.model.TripCategory; +import org.springframework.stereotype.Service; + +@Service +public class DummyTripCommandService { + + public Trip createDummyTrip(Member member, String category, int count) { + CreateDummyTripCommand command = + CreateDummyTripCommandGenerator.of(TripCategory.from(category)); + + return TripFactory.create( + member, + command.name(), + command.memo(), + command.tripCategory(), + command.endDate(), + count); + } +} diff --git a/src/main/java/com/ject/studytrip/dummy/presentation/controller/DummyMissionController.java b/src/main/java/com/ject/studytrip/dummy/presentation/controller/DummyMissionController.java new file mode 100644 index 0000000..bd7c376 --- /dev/null +++ b/src/main/java/com/ject/studytrip/dummy/presentation/controller/DummyMissionController.java @@ -0,0 +1,52 @@ +package com.ject.studytrip.dummy.presentation.controller; + +import com.ject.studytrip.dummy.application.dto.DummyMissionsInfo; +import com.ject.studytrip.dummy.application.facade.DummyMissionFacade; +import com.ject.studytrip.dummy.presentation.dto.response.LoadDummyMissionInfoResponse; +import com.ject.studytrip.global.common.response.StandardResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +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.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "Dummy Mission", description = "더미 미션 API") +@RestController +@RequestMapping("/api/dummies/missions") +@RequiredArgsConstructor +@Validated +public class DummyMissionController { + private final DummyMissionFacade dummyMissionFacade; + + @Operation( + summary = "더미 미션 목록 조회", + description = "여행 카테고리와 생성할 더미 데이터 개수를 이용해 더미 미션 목록을 조회합니다.(DB 저장 X)") + @GetMapping + public ResponseEntity loadDummyMissions( + @AuthenticationPrincipal String memberId, + @RequestParam + @NotNull(message = "여행 카테고리는 필수 요청 값입니다.") + @Pattern( + regexp = "^(COURSE|EXPLORE)$", + message = "여행 카테고리는 COURSE, EXPLORE 중 하나여야 합니다.") + String category, + @RequestParam @Min(value = 1, message = "count는 1 이상이어야 합니다.") int count) { + DummyMissionsInfo result = + dummyMissionFacade.generateDummyMissions(Long.valueOf(memberId), category, count); + List responses = + result.missionInfos().stream().map(LoadDummyMissionInfoResponse::of).toList(); + + return ResponseEntity.status(HttpStatus.OK) + .body(StandardResponse.success(HttpStatus.OK.value(), responses)); + } +} diff --git a/src/main/java/com/ject/studytrip/dummy/presentation/controller/DummyStampController.java b/src/main/java/com/ject/studytrip/dummy/presentation/controller/DummyStampController.java new file mode 100644 index 0000000..b8bd220 --- /dev/null +++ b/src/main/java/com/ject/studytrip/dummy/presentation/controller/DummyStampController.java @@ -0,0 +1,52 @@ +package com.ject.studytrip.dummy.presentation.controller; + +import com.ject.studytrip.dummy.application.dto.DummyStampsInfo; +import com.ject.studytrip.dummy.application.facade.DummyStampFacade; +import com.ject.studytrip.dummy.presentation.dto.response.LoadDummyStampInfoResponse; +import com.ject.studytrip.global.common.response.StandardResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +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.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "Dummy Stamp", description = "더미 스탬프 API") +@RestController +@RequestMapping("/api/dummies/stamps") +@RequiredArgsConstructor +@Validated +public class DummyStampController { + private final DummyStampFacade dummyStampFacade; + + @Operation( + summary = "더미 스탬프 목록 조회", + description = "여행 카테고리와 생성할 더미 데이터 개수를 이용해 더미 스탬프 목록을 조회합니다.") + @GetMapping + public ResponseEntity loadDummyStamps( + @AuthenticationPrincipal String memberId, + @RequestParam + @NotNull(message = "여행 카테고리는 필수 요청 값입니다.") + @Pattern( + regexp = "^(COURSE|EXPLORE)$", + message = "여행 카테고리는 COURSE, EXPLORE 중 하나여야 합니다.") + String category, + @RequestParam @Min(value = 1, message = "count는 1 이상이어야 합니다.") int count) { + DummyStampsInfo result = + dummyStampFacade.generateDummyStamps(Long.valueOf(memberId), category, count); + List responses = + result.stampsInfos().stream().map(LoadDummyStampInfoResponse::of).toList(); + + return ResponseEntity.status(HttpStatus.OK) + .body(StandardResponse.success(HttpStatus.OK.value(), responses)); + } +} diff --git a/src/main/java/com/ject/studytrip/dummy/presentation/dto/response/LoadDummyMissionInfoResponse.java b/src/main/java/com/ject/studytrip/dummy/presentation/dto/response/LoadDummyMissionInfoResponse.java new file mode 100644 index 0000000..3dbcac8 --- /dev/null +++ b/src/main/java/com/ject/studytrip/dummy/presentation/dto/response/LoadDummyMissionInfoResponse.java @@ -0,0 +1,14 @@ +package com.ject.studytrip.dummy.presentation.dto.response; + +import com.ject.studytrip.dummy.application.dto.DummyMissionInfo; +import io.swagger.v3.oas.annotations.media.Schema; + +public record LoadDummyMissionInfoResponse( + @Schema(description = "미션 ID") Long missionId, + @Schema(description = "미션 이름") String missionName, + @Schema(description = "미션 완료여부") boolean completed) { + public static LoadDummyMissionInfoResponse of(DummyMissionInfo info) { + return new LoadDummyMissionInfoResponse( + info.missionId(), info.missionName(), info.completed()); + } +} diff --git a/src/main/java/com/ject/studytrip/dummy/presentation/dto/response/LoadDummyStampInfoResponse.java b/src/main/java/com/ject/studytrip/dummy/presentation/dto/response/LoadDummyStampInfoResponse.java new file mode 100644 index 0000000..f86ffb1 --- /dev/null +++ b/src/main/java/com/ject/studytrip/dummy/presentation/dto/response/LoadDummyStampInfoResponse.java @@ -0,0 +1,24 @@ +package com.ject.studytrip.dummy.presentation.dto.response; + +import com.ject.studytrip.dummy.application.dto.DummyStampInfo; +import io.swagger.v3.oas.annotations.media.Schema; + +public record LoadDummyStampInfoResponse( + @Schema(description = "스탬프 ID") Long stampId, + @Schema(description = "스탬프 이름") String stampName, + @Schema(description = "스탬프 순서") int stampOrder, + @Schema(description = "스탬프 종료일") String endDate, + @Schema(description = "스탬프에 속한 총 미션 개수") int totalMissions, + @Schema(description = "스탬프에 속한 완료된 미션 개수") int completedMissions, + @Schema(description = "스탬프 완료 여부") boolean completed) { + public static LoadDummyStampInfoResponse of(DummyStampInfo info) { + return new LoadDummyStampInfoResponse( + info.stampId(), + info.stampName(), + info.stampOrder(), + info.endDate(), + info.totalMissions(), + info.completedMissions(), + info.completed()); + } +} diff --git a/src/test/java/com/ject/studytrip/dummy/application/service/DummyMissionCommandServiceTest.java b/src/test/java/com/ject/studytrip/dummy/application/service/DummyMissionCommandServiceTest.java new file mode 100644 index 0000000..821ba76 --- /dev/null +++ b/src/test/java/com/ject/studytrip/dummy/application/service/DummyMissionCommandServiceTest.java @@ -0,0 +1,52 @@ +package com.ject.studytrip.dummy.application.service; + +import static org.assertj.core.api.Assertions.assertThat; + +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.Mission; +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 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; + +@DisplayName("DummyMissionCommandService 단위 테스트") +class DummyMissionCommandServiceTest extends BaseUnitTest { + private static final int COUNT = 10; + + @InjectMocks private DummyMissionCommandService dummyMissionCommandService; + + private Trip courseTrip; + + @BeforeEach + void setUp() { + Member member = MemberFixture.createMemberFromKakao(); + courseTrip = TripFixture.createTrip(member, TripCategory.COURSE); + } + + @Nested + @DisplayName("createDummyMission 메서드는") + class CreateDummyMission { + + @Test + @DisplayName("특정 스탬프가 들어오면 더미 미션을 생성하고 리턴한다.") + void shouldReturnDummyMissionForStamp() { + // given + Stamp stamp = StampFixture.createStamp(courseTrip, COUNT); + + // when + Mission result = dummyMissionCommandService.createDummyMission(stamp); + + // then + assertThat(result).isNotNull(); + assertThat(result.getName()).isNotNull(); + } + } +} diff --git a/src/test/java/com/ject/studytrip/dummy/application/service/DummyStampCommandServiceTest.java b/src/test/java/com/ject/studytrip/dummy/application/service/DummyStampCommandServiceTest.java new file mode 100644 index 0000000..b890bb9 --- /dev/null +++ b/src/test/java/com/ject/studytrip/dummy/application/service/DummyStampCommandServiceTest.java @@ -0,0 +1,67 @@ +package com.ject.studytrip.dummy.application.service; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ject.studytrip.BaseUnitTest; +import com.ject.studytrip.member.domain.model.Member; +import com.ject.studytrip.member.fixture.MemberFixture; +import com.ject.studytrip.stamp.domain.model.Stamp; +import com.ject.studytrip.trip.domain.model.Trip; +import com.ject.studytrip.trip.domain.model.TripCategory; +import com.ject.studytrip.trip.fixture.TripFixture; +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; + +@DisplayName("DummyStampCommandService 단위 테스트") +class DummyStampCommandServiceTest extends BaseUnitTest { + private static final int COUNT = 10; + + @InjectMocks private DummyStampCommandService dummyStampCommandService; + + private Member member; + + @BeforeEach + void setUp() { + member = MemberFixture.createMemberFromKakao(); + } + + @Nested + @DisplayName("createDummyStamp 메서드는") + class CreateDummyStamp { + + @Test + @DisplayName("코스형 여행이 들어오면 코스형 더미 스탬프를 생성하고 반환한다.") + void shouldReturnDummyCourseStampForCourseTrip() { + // given + Trip courseTrip = TripFixture.createTrip(member, TripCategory.COURSE); + + // when + Stamp result = dummyStampCommandService.createDummyStamp(courseTrip, COUNT); + + // then + assertThat(result).isNotNull(); + assertThat(result.getName()).isNotNull(); + assertThat(result.getStampOrder()).isPositive(); // 양수 + assertThat(result.getEndDate()).isNotNull(); + } + + @Test + @DisplayName("탐험형 여행이 들어오면 탐험형 더미 스탬프를 생성하고 반환한다.") + void shouldReturnDummyExploreStampForExploreTrip() { + // given + Trip exploreTrip = TripFixture.createTrip(member, TripCategory.EXPLORE); + + // when + Stamp result = dummyStampCommandService.createDummyStamp(exploreTrip, COUNT); + + // then + assertThat(result).isNotNull(); + assertThat(result.getName()).isNotNull(); + assertThat(result.getStampOrder()).isZero(); + assertThat(result.getEndDate()).isNull(); + } + } +} diff --git a/src/test/java/com/ject/studytrip/dummy/application/service/DummyTripCommandServiceTest.java b/src/test/java/com/ject/studytrip/dummy/application/service/DummyTripCommandServiceTest.java new file mode 100644 index 0000000..44ed6f0 --- /dev/null +++ b/src/test/java/com/ject/studytrip/dummy/application/service/DummyTripCommandServiceTest.java @@ -0,0 +1,68 @@ +package com.ject.studytrip.dummy.application.service; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ject.studytrip.BaseUnitTest; +import com.ject.studytrip.member.domain.model.Member; +import com.ject.studytrip.member.fixture.MemberFixture; +import com.ject.studytrip.trip.domain.model.Trip; +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; + +@DisplayName("DummyTripCommandService 단위 테스트") +class DummyTripCommandServiceTest extends BaseUnitTest { + private static final int COUNT = 10; + + @InjectMocks private DummyTripCommandService dummyTripCommandService; + + private Member member; + + @BeforeEach + void setUp() { + member = MemberFixture.createMemberFromKakao(); + } + + @Nested + @DisplayName("createDummyTrip 메서드는") + class CreateDummyTrip { + + @Test + @DisplayName("COURSE 카테고리가 들어오면 코스형 더미 여행을 생성하고 반환한다.") + void shouldReturnDummyCourseTripWhenCategoryIsCourse() { + // given + String category = "COURSE"; + + // when + Trip result = dummyTripCommandService.createDummyTrip(member, category, COUNT); + + // then + assertThat(result).isNotNull(); + assertThat(result.getName()).isNotNull(); + assertThat(result.getMemo()).isNotNull(); + assertThat(result.getCategory()).isNotNull(); + assertThat(result.getStartDate()).isNotNull(); + assertThat(result.getEndDate()).isNotNull(); + } + + @Test + @DisplayName("EXPLORE 카테고리가 들어오면 탐험형 더미 여행을 생성하고 반환한다.") + void shouldReturnDummyExploreTripWhenCategoryIsExplore() { + // given + String category = "EXPLORE"; + + // when + Trip result = dummyTripCommandService.createDummyTrip(member, category, COUNT); + + // then + assertThat(result).isNotNull(); + assertThat(result.getName()).isNotNull(); + assertThat(result.getMemo()).isNotNull(); + assertThat(result.getCategory()).isNotNull(); + assertThat(result.getStartDate()).isNotNull(); + assertThat(result.getEndDate()).isNull(); + } + } +} diff --git a/src/test/java/com/ject/studytrip/dummy/presentation/controller/DummyMissionControllerIntegrationTest.java b/src/test/java/com/ject/studytrip/dummy/presentation/controller/DummyMissionControllerIntegrationTest.java new file mode 100644 index 0000000..22da293 --- /dev/null +++ b/src/test/java/com/ject/studytrip/dummy/presentation/controller/DummyMissionControllerIntegrationTest.java @@ -0,0 +1,171 @@ +package com.ject.studytrip.dummy.presentation.controller; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +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.error.MemberErrorCode; +import com.ject.studytrip.member.domain.model.Member; +import com.ject.studytrip.member.helper.MemberTestHelper; +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("DummyMissionController 통합 테스트") +class DummyMissionControllerIntegrationTest extends BaseIntegrationTest { + private static final String BASE_DUMMY_MISSION_URL = "/api/dummies/missions"; + private static final String COURSE_CATEGORY = "COURSE"; + private static final String EXPLORE_CATEGORY = "EXPLORE"; + private static final int COUNT = 10; + + @Autowired private MemberTestHelper memberTestHelper; + @Autowired private TokenTestHelper tokenTestHelper; + + private Member member; + private String accessToken; + + @BeforeEach + void setUp() { + member = memberTestHelper.saveMember(); + accessToken = + tokenTestHelper.createAccessToken( + member.getId().toString(), member.getRole().name()); + } + + @Nested + @DisplayName("더미 미션 목록 조회 API") + class LoadDummyMissions { + private ResultActions getResultActions(String accessToken, String category, int count) + throws Exception { + return mockMvc.perform( + get(BASE_DUMMY_MISSION_URL) + .header( + HttpHeaders.AUTHORIZATION, + TokenFixture.TOKEN_PREFIX + accessToken) + .param("category", category) + .param("count", String.valueOf(count)) + .contentType(MediaType.APPLICATION_JSON)); + } + + @Test + @DisplayName("Access Token이 없으면 401 Unauthorized를 반환한다.") + void shouldReturnUnauthorizedWhenAccessTokenIsMissing() throws Exception { + // when + ResultActions resultActions = getResultActions("", COURSE_CATEGORY, COUNT); + + // then + resultActions + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.success").value(false)) + .andExpect( + jsonPath("$.status") + .value(AuthErrorCode.UNAUTHENTICATED.getStatus().value())) + .andExpect( + jsonPath("$.data.message") + .value(AuthErrorCode.UNAUTHENTICATED.getMessage())); + } + + @Test + @DisplayName("유효하지 않은 카테고리가 들어오면 400 Bad Request를 반환한다.") + void shouldReturnBadRequestWhenCategoryIsInvalid() throws Exception { + // given + String invalidCategory = "INVALID"; + + // when + ResultActions resultActions = getResultActions(accessToken, invalidCategory, COUNT); + + // 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 shouldReturnBadRequestWhenCountIsInvalid() throws Exception { + // given + int invalidCount = 0; + + // when + ResultActions resultActions = + getResultActions(accessToken, COURSE_CATEGORY, invalidCount); + + // then + resultActions + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.success").value(false)) + .andExpect( + jsonPath("$.status") + .value( + CommonErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH + .getStatus() + .value())); + } + + @Test + @DisplayName("삭제된 사용자일 경우 404 Not Found를 반환한다.") + void shouldReturnBadRequestWhenMemberAlreadyDeleted() throws Exception { + // given + member.updateDeletedAt(); + + // when + ResultActions resultActions = getResultActions(accessToken, COURSE_CATEGORY, COUNT); + + // then + resultActions + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.success").value(false)) + .andExpect( + jsonPath("$.status") + .value(MemberErrorCode.MEMBER_NOT_FOUND.getStatus().value())) + .andExpect( + jsonPath("$.data.message") + .value(MemberErrorCode.MEMBER_NOT_FOUND.getMessage())); + } + + @Test + @DisplayName("COURSE 카테고리가 들어오면 더미 미션 목록를 반환한다.(DB 저장 X)") + void shouldReturnDummyMissionsWhenCategoryIsCourse() throws Exception { + // when + ResultActions resultActions = getResultActions(accessToken, COURSE_CATEGORY, COUNT); + + // then + resultActions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.status").value(HttpStatus.OK.value())) + .andExpect(jsonPath("$.data").isArray()); + } + + @Test + @DisplayName("EXPLORE 카테고리가 들어오면 더미 미션 목록를 반환한다.(DB 저장 X)") + void shouldReturnDummyMissionsWhenCategoryIsExplore() throws Exception { + // when + ResultActions resultActions = getResultActions(accessToken, EXPLORE_CATEGORY, COUNT); + + // then + resultActions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.status").value(HttpStatus.OK.value())) + .andExpect(jsonPath("$.data").isArray()); + } + } +} diff --git a/src/test/java/com/ject/studytrip/dummy/presentation/controller/DummyStampControllerIntegrationTest.java b/src/test/java/com/ject/studytrip/dummy/presentation/controller/DummyStampControllerIntegrationTest.java new file mode 100644 index 0000000..2268e48 --- /dev/null +++ b/src/test/java/com/ject/studytrip/dummy/presentation/controller/DummyStampControllerIntegrationTest.java @@ -0,0 +1,173 @@ +package com.ject.studytrip.dummy.presentation.controller; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +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.error.MemberErrorCode; +import com.ject.studytrip.member.domain.model.Member; +import com.ject.studytrip.member.helper.MemberTestHelper; +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("DummyStampController 통합 테스트") +class DummyStampControllerIntegrationTest extends BaseIntegrationTest { + private static final String BASE_DUMMY_STAMP_URL = "/api/dummies/stamps"; + private static final String COURSE_CATEGORY = "COURSE"; + private static final String EXPLORE_CATEGORY = "EXPLORE"; + private static final int COUNT = 10; + + @Autowired private MemberTestHelper memberTestHelper; + @Autowired private TokenTestHelper tokenTestHelper; + + private Member member; + private String accessToken; + + @BeforeEach + void setUp() { + member = memberTestHelper.saveMember(); + accessToken = + tokenTestHelper.createAccessToken( + member.getId().toString(), member.getRole().name()); + } + + @Nested + @DisplayName("더미 스탬프 목록 조회 API") + class LoadDummyMissions { + private ResultActions getResultActions(String accessToken, String category, int count) + throws Exception { + return mockMvc.perform( + get(BASE_DUMMY_STAMP_URL) + .header( + HttpHeaders.AUTHORIZATION, + TokenFixture.TOKEN_PREFIX + accessToken) + .param("category", category) + .param("count", String.valueOf(count)) + .contentType(MediaType.APPLICATION_JSON)); + } + + @Test + @DisplayName("Access Token이 없으면 401 Unauthorized를 반환한다.") + void shouldReturnUnauthorizedWhenAccessTokenIsMissing() throws Exception { + // when + ResultActions resultActions = getResultActions("", COURSE_CATEGORY, COUNT); + + // then + resultActions + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.success").value(false)) + .andExpect( + jsonPath("$.status") + .value(AuthErrorCode.UNAUTHENTICATED.getStatus().value())) + .andExpect( + jsonPath("$.data.message") + .value(AuthErrorCode.UNAUTHENTICATED.getMessage())); + } + + @Test + @DisplayName("유효하지 않은 카테고리가 들어오면 400 Bad Request를 반환한다.") + void shouldReturnBadRequestWhenCategoryIsInvalid() throws Exception { + // given + String invalidCategory = "INVALID"; + + // when + ResultActions resultActions = getResultActions(accessToken, invalidCategory, COUNT); + + // 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 shouldReturnBadRequestWhenCountIsInvalid() throws Exception { + // given + int invalidCount = 0; + + // when + ResultActions resultActions = + getResultActions(accessToken, COURSE_CATEGORY, invalidCount); + + // then + resultActions + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.success").value(false)) + .andExpect( + jsonPath("$.status") + .value( + CommonErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH + .getStatus() + .value())); + } + + @Test + @DisplayName("삭제된 사용자일 경우 404 Not Found를 반환한다.") + void shouldReturnBadRequestWhenMemberAlreadyDeleted() throws Exception { + // given + member.updateDeletedAt(); + + // when + ResultActions resultActions = getResultActions(accessToken, COURSE_CATEGORY, COUNT); + + // then + resultActions + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.success").value(false)) + .andExpect( + jsonPath("$.status") + .value(MemberErrorCode.MEMBER_NOT_FOUND.getStatus().value())) + .andExpect( + jsonPath("$.data.message") + .value(MemberErrorCode.MEMBER_NOT_FOUND.getMessage())); + } + + @Test + @DisplayName("COURSE 카테고리가 들어오면 코스형 더미 스탬프 목록를 반환한다.(DB 저장 X)") + void shouldReturnDummyCourseStampsWhenCategoryIsCourse() throws Exception { + // when + ResultActions resultActions = getResultActions(accessToken, COURSE_CATEGORY, COUNT); + + // then + resultActions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.status").value(HttpStatus.OK.value())) + .andExpect(jsonPath("$.data").isArray()) + .andExpect(jsonPath("$.data[0].stampOrder").value(1)); + } + + @Test + @DisplayName("EXPLORE 카테고리가 들어오면 탐험형 더미 스탬프 목록를 반환한다.(DB 저장 X)") + void shouldReturnDummyExploreStampsWhenCategoryIsExplore() throws Exception { + // when + ResultActions resultActions = getResultActions(accessToken, EXPLORE_CATEGORY, COUNT); + + // then + resultActions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.status").value(HttpStatus.OK.value())) + .andExpect(jsonPath("$.data").isArray()) + .andExpect(jsonPath("$.data[0].stampOrder").value(0)); + } + } +}