diff --git a/backend/src/main/java/kr/touroot/member/service/MyPageFacadeService.java b/backend/src/main/java/kr/touroot/member/service/MyPageFacadeService.java index 276db06b..802f7db2 100644 --- a/backend/src/main/java/kr/touroot/member/service/MyPageFacadeService.java +++ b/backend/src/main/java/kr/touroot/member/service/MyPageFacadeService.java @@ -43,7 +43,7 @@ public Page readTravelPlans(MemberAuth memberAuth, Pageable pageab Member member = memberService.getById(memberAuth.memberId()); Page travelPlans = travelPlanService.getAllByAuthor(member, pageable); - return travelPlans.map((travelPlanService::getTravelPlanResponse)); + return travelPlans.map(PlanResponse::from); } @Transactional diff --git a/backend/src/main/java/kr/touroot/travelplan/controller/TravelPlanController.java b/backend/src/main/java/kr/touroot/travelplan/controller/TravelPlanController.java index 75416e50..1df6b669 100644 --- a/backend/src/main/java/kr/touroot/travelplan/controller/TravelPlanController.java +++ b/backend/src/main/java/kr/touroot/travelplan/controller/TravelPlanController.java @@ -15,7 +15,7 @@ import kr.touroot.travelplan.dto.request.PlanRequest; import kr.touroot.travelplan.dto.response.PlanCreateResponse; import kr.touroot.travelplan.dto.response.PlanResponse; -import kr.touroot.travelplan.service.TravelPlanService; +import kr.touroot.travelplan.service.TravelPlanFacadeService; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; @@ -33,7 +33,7 @@ @RequestMapping("/api/v1/travel-plans") public class TravelPlanController { - private final TravelPlanService travelPlanService; + private final TravelPlanFacadeService travelPlanFacadeService; @Operation(summary = "여행 계획 생성") @ApiResponses(value = { @@ -57,7 +57,7 @@ public ResponseEntity createTravelPlan( @Valid @RequestBody PlanRequest request, MemberAuth memberAuth ) { - PlanCreateResponse data = travelPlanService.createTravelPlan(request, memberAuth); + PlanCreateResponse data = travelPlanFacadeService.createTravelPlan(request, memberAuth); return ResponseEntity.created(URI.create("/api/v1/travel-plans/" + data.id())).build(); } @@ -83,7 +83,7 @@ public ResponseEntity readTravelPlan( @Parameter(description = "여행 계획 id") @PathVariable Long id, MemberAuth memberAuth ) { - PlanResponse data = travelPlanService.readTravelPlan(id, memberAuth); + PlanResponse data = travelPlanFacadeService.findTravelPlanById(id, memberAuth); return ResponseEntity.ok(data); } @@ -110,7 +110,7 @@ public ResponseEntity updateTravelPlan( @Valid MemberAuth memberAuth, @Valid @RequestBody PlanRequest request ) { - travelPlanService.updateTravelPlan(id, memberAuth, request); + travelPlanFacadeService.updateTravelPlanById(id, memberAuth, request); return ResponseEntity.ok().build(); } @@ -133,7 +133,7 @@ public ResponseEntity updateTravelPlan( }) @DeleteMapping("/{id}") public ResponseEntity deleteTravelPlan(@PathVariable Long id, MemberAuth memberAuth) { - travelPlanService.deleteByTravelPlanId(id, memberAuth); + travelPlanFacadeService.deleteTravelPlanById(id, memberAuth); return ResponseEntity.noContent() .build(); } @@ -154,7 +154,7 @@ public ResponseEntity deleteTravelPlan(@PathVariable Long id, MemberAuth m public ResponseEntity readSharedTravelPlan( @Parameter(description = "여행 계획 공유 키") @PathVariable UUID shareKey ) { - PlanResponse data = travelPlanService.readTravelPlan(shareKey); + PlanResponse data = travelPlanFacadeService.findTravelPlanByShareKey(shareKey); return ResponseEntity.ok(data); } } diff --git a/backend/src/main/java/kr/touroot/travelplan/domain/TravelPlaceTodo.java b/backend/src/main/java/kr/touroot/travelplan/domain/TravelPlaceTodo.java index 703e39be..011e0980 100644 --- a/backend/src/main/java/kr/touroot/travelplan/domain/TravelPlaceTodo.java +++ b/backend/src/main/java/kr/touroot/travelplan/domain/TravelPlaceTodo.java @@ -93,4 +93,8 @@ private void validateOrderNonNegative(Integer order) { public void updateCheckedStatus(boolean checkedStatus) { isChecked = checkedStatus; } + + public void updateTravelPlanPlace(TravelPlanPlace travelPlanPlace) { + this.travelPlanPlace = travelPlanPlace; + } } diff --git a/backend/src/main/java/kr/touroot/travelplan/domain/TravelPlan.java b/backend/src/main/java/kr/touroot/travelplan/domain/TravelPlan.java index c7e490a1..f44024ad 100644 --- a/backend/src/main/java/kr/touroot/travelplan/domain/TravelPlan.java +++ b/backend/src/main/java/kr/touroot/travelplan/domain/TravelPlan.java @@ -98,6 +98,16 @@ public void update(String title, LocalDate startDate) { this.startDate = startDate; } + public void updateDays(List travelPlanDays) { + this.travelPlanDays.clear(); + travelPlanDays.forEach(this::addDay); + } + + public void addDay(TravelPlanDay day) { + travelPlanDays.add(day); + day.updatePlan(this); + } + public boolean isStartDateBefore(LocalDate date) { return startDate.isBefore(date); } diff --git a/backend/src/main/java/kr/touroot/travelplan/domain/TravelPlanDay.java b/backend/src/main/java/kr/touroot/travelplan/domain/TravelPlanDay.java index 5a56cbf6..f29ed37b 100644 --- a/backend/src/main/java/kr/touroot/travelplan/domain/TravelPlanDay.java +++ b/backend/src/main/java/kr/touroot/travelplan/domain/TravelPlanDay.java @@ -33,7 +33,7 @@ public class TravelPlanDay extends BaseEntity { private Long id; @Column(name = "PLAN_DAY_ORDER", nullable = false) - Integer order; + private Integer order; @JoinColumn(name = "PLAN_ID", nullable = false) @ManyToOne(fetch = FetchType.LAZY) @@ -70,8 +70,17 @@ private void validateOrderRange(Integer order) { } } + public void addPlace(TravelPlanPlace place) { + travelPlanPlaces.add(place); + place.updateDay(this); + } + public LocalDate getCurrentDate() { LocalDate startDate = plan.getStartDate(); return startDate.plusDays(order); } + + public void updatePlan(TravelPlan plan) { + this.plan = plan; + } } diff --git a/backend/src/main/java/kr/touroot/travelplan/domain/TravelPlanPlace.java b/backend/src/main/java/kr/touroot/travelplan/domain/TravelPlanPlace.java index 407015f5..256019a5 100644 --- a/backend/src/main/java/kr/touroot/travelplan/domain/TravelPlanPlace.java +++ b/backend/src/main/java/kr/touroot/travelplan/domain/TravelPlanPlace.java @@ -100,4 +100,13 @@ private void validatePlaceNameLength(String placeName) { throw new BadRequestException("장소 이름은 " + PLACE_NAME_MAX_LENGTH + "자 이하여야 합니다"); } } + + public void addTodo(TravelPlaceTodo todo) { + travelPlaceTodos.add(todo); + todo.updateTravelPlanPlace(this); + } + + public void updateDay(TravelPlanDay day) { + this.day = day; + } } diff --git a/backend/src/main/java/kr/touroot/travelplan/dto/request/PlanDayRequest.java b/backend/src/main/java/kr/touroot/travelplan/dto/request/PlanDayRequest.java index f2c83802..20b40105 100644 --- a/backend/src/main/java/kr/touroot/travelplan/dto/request/PlanDayRequest.java +++ b/backend/src/main/java/kr/touroot/travelplan/dto/request/PlanDayRequest.java @@ -7,6 +7,7 @@ import java.util.List; import kr.touroot.travelplan.domain.TravelPlan; import kr.touroot.travelplan.domain.TravelPlanDay; +import kr.touroot.travelplan.domain.TravelPlanPlace; public record PlanDayRequest( @Schema(description = "여행 장소 정보") @@ -17,6 +18,16 @@ public record PlanDayRequest( ) { public TravelPlanDay toPlanDay(int order, TravelPlan plan) { - return new TravelPlanDay(order, plan); + TravelPlanDay travelPlanDay = new TravelPlanDay(order, plan); + addPlaces(travelPlanDay); + return travelPlanDay; + } + + private void addPlaces(TravelPlanDay travelPlanDay) { + for (int order = 0; order < places.size(); order++) { + PlanPlaceRequest planPlaceRequest = places.get(order); + TravelPlanPlace planPlace = planPlaceRequest.toPlanPlace(order, travelPlanDay); + travelPlanDay.addPlace(planPlace); + } } } diff --git a/backend/src/main/java/kr/touroot/travelplan/dto/request/PlanPlaceRequest.java b/backend/src/main/java/kr/touroot/travelplan/dto/request/PlanPlaceRequest.java index 63cd09a5..97f5645d 100644 --- a/backend/src/main/java/kr/touroot/travelplan/dto/request/PlanPlaceRequest.java +++ b/backend/src/main/java/kr/touroot/travelplan/dto/request/PlanPlaceRequest.java @@ -5,6 +5,7 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import java.util.List; +import kr.touroot.travelplan.domain.TravelPlaceTodo; import kr.touroot.travelplan.domain.TravelPlanDay; import kr.touroot.travelplan.domain.TravelPlanPlace; import lombok.Builder; @@ -22,6 +23,22 @@ public record PlanPlaceRequest( ) { public TravelPlanPlace toPlanPlace(int order, TravelPlanDay day) { - return new TravelPlanPlace(order, day, placeName, position().lat(), position().lng()); + TravelPlanPlace travelPlanPlace = new TravelPlanPlace( + order, + day, + placeName, + position().lat(), + position().lng() + ); + addTodos(travelPlanPlace); + return travelPlanPlace; + } + + private void addTodos(TravelPlanPlace travelPlanPlace) { + for (int order = 0; order < todos.size(); order++) { + PlanPlaceTodoRequest todoRequest = todos.get(order); + TravelPlaceTodo placeTodo = todoRequest.toPlaceTodo(travelPlanPlace, order); + travelPlanPlace.addTodo(placeTodo); + } } } diff --git a/backend/src/main/java/kr/touroot/travelplan/dto/request/PlanRequest.java b/backend/src/main/java/kr/touroot/travelplan/dto/request/PlanRequest.java index 9cd1d56e..95b3ee60 100644 --- a/backend/src/main/java/kr/touroot/travelplan/dto/request/PlanRequest.java +++ b/backend/src/main/java/kr/touroot/travelplan/dto/request/PlanRequest.java @@ -6,10 +6,12 @@ import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import java.time.LocalDate; +import java.util.ArrayList; import java.util.List; import java.util.UUID; import kr.touroot.member.domain.Member; import kr.touroot.travelplan.domain.TravelPlan; +import kr.touroot.travelplan.domain.TravelPlanDay; import lombok.Builder; @Builder @@ -28,6 +30,27 @@ public record PlanRequest( ) { public TravelPlan toTravelPlan(Member author, UUID shareKey) { - return new TravelPlan(title, startDate, shareKey, author); + TravelPlan travelPlan = new TravelPlan(title, startDate, shareKey, author); + addDays(travelPlan); + return travelPlan; + } + + private void addDays(TravelPlan travelPlan) { + for (int order = 0; order < days.size(); order++) { + PlanDayRequest planDayRequest = days.get(order); + TravelPlanDay planDay = planDayRequest.toPlanDay(order, travelPlan); + travelPlan.addDay(planDay); + } + } + + public List getDays(TravelPlan travelPlan) { + List travelPlanDays = new ArrayList<>(); + for (int order = 0; order < days.size(); order++) { + PlanDayRequest planDayRequest = days.get(order); + TravelPlanDay planDay = planDayRequest.toPlanDay(order, travelPlan); + travelPlanDays.add(planDay); + } + + return travelPlanDays; } } diff --git a/backend/src/main/java/kr/touroot/travelplan/dto/response/PlanCreateResponse.java b/backend/src/main/java/kr/touroot/travelplan/dto/response/PlanCreateResponse.java index f742bb72..cb2bcbea 100644 --- a/backend/src/main/java/kr/touroot/travelplan/dto/response/PlanCreateResponse.java +++ b/backend/src/main/java/kr/touroot/travelplan/dto/response/PlanCreateResponse.java @@ -1,9 +1,14 @@ package kr.touroot.travelplan.dto.response; import io.swagger.v3.oas.annotations.media.Schema; +import kr.touroot.travelplan.domain.TravelPlan; public record PlanCreateResponse( @Schema(description = "생성된 여행 계획 id", example = "1") Long id ) { + + public static PlanCreateResponse from(TravelPlan travelPlan) { + return new PlanCreateResponse(travelPlan.getId()); + } } diff --git a/backend/src/main/java/kr/touroot/travelplan/dto/response/PlanDayResponse.java b/backend/src/main/java/kr/touroot/travelplan/dto/response/PlanDayResponse.java index 1b90a266..e34906f4 100644 --- a/backend/src/main/java/kr/touroot/travelplan/dto/response/PlanDayResponse.java +++ b/backend/src/main/java/kr/touroot/travelplan/dto/response/PlanDayResponse.java @@ -13,14 +13,17 @@ public record PlanDayResponse( @Schema(description = "여행 장소별 정보") List places ) { - public static PlanDayResponse of( - TravelPlanDay planDay, - List places - ) { + public static PlanDayResponse from(TravelPlanDay planDay) { return PlanDayResponse.builder() .id(planDay.getId()) .date(planDay.getCurrentDate()) - .places(places) + .places(getTravelPlanPlaceResponse(planDay)) .build(); } + + public static List getTravelPlanPlaceResponse(TravelPlanDay day) { + return day.getTravelPlanPlaces().stream() + .map(PlanPlaceResponse::from) + .toList(); + } } diff --git a/backend/src/main/java/kr/touroot/travelplan/dto/response/PlanPlaceResponse.java b/backend/src/main/java/kr/touroot/travelplan/dto/response/PlanPlaceResponse.java index 435c9699..235a9e0c 100644 --- a/backend/src/main/java/kr/touroot/travelplan/dto/response/PlanPlaceResponse.java +++ b/backend/src/main/java/kr/touroot/travelplan/dto/response/PlanPlaceResponse.java @@ -13,12 +13,18 @@ public record PlanPlaceResponse( @Schema(description = "여행 장소 TODO") List todos ) { - public static PlanPlaceResponse of(TravelPlanPlace planPlace, List todos) { + public static PlanPlaceResponse from(TravelPlanPlace planPlace) { return PlanPlaceResponse.builder() .id(planPlace.getId()) .placeName(planPlace.getName()) .position(PlanPositionResponse.from(planPlace.getPosition())) - .todos(todos) + .todos(getTodoResponse(planPlace)) .build(); } + + private static List getTodoResponse(TravelPlanPlace planPlace) { + return planPlace.getTravelPlaceTodos().stream() + .map(PlanPlaceTodoResponse::from) + .toList(); + } } diff --git a/backend/src/main/java/kr/touroot/travelplan/dto/response/PlanResponse.java b/backend/src/main/java/kr/touroot/travelplan/dto/response/PlanResponse.java index b3f9937a..30781b9f 100644 --- a/backend/src/main/java/kr/touroot/travelplan/dto/response/PlanResponse.java +++ b/backend/src/main/java/kr/touroot/travelplan/dto/response/PlanResponse.java @@ -16,13 +16,19 @@ public record PlanResponse( @Schema(description = "여행 계획 공유 share Key") UUID shareKey ) { - public static PlanResponse of(TravelPlan travelPlan, List days) { + public static PlanResponse from(TravelPlan travelPlan) { return PlanResponse.builder() .id(travelPlan.getId()) .title(travelPlan.getTitle()) .startDate(travelPlan.getStartDate()) - .days(days) + .days(getTravelPlanDaysResponse(travelPlan)) .shareKey(travelPlan.getShareKey()) .build(); } + + private static List getTravelPlanDaysResponse(TravelPlan travelPlan) { + return travelPlan.getTravelPlanDays().stream() + .map(PlanDayResponse::from) + .toList(); + } } diff --git a/backend/src/main/java/kr/touroot/travelplan/repository/PlaceTodoRepository.java b/backend/src/main/java/kr/touroot/travelplan/repository/PlaceTodoRepository.java index 21467827..8e1cec56 100644 --- a/backend/src/main/java/kr/touroot/travelplan/repository/PlaceTodoRepository.java +++ b/backend/src/main/java/kr/touroot/travelplan/repository/PlaceTodoRepository.java @@ -1,14 +1,7 @@ package kr.touroot.travelplan.repository; -import java.util.List; import kr.touroot.travelplan.domain.TravelPlaceTodo; -import kr.touroot.travelplan.domain.TravelPlan; -import kr.touroot.travelplan.domain.TravelPlanPlace; import org.springframework.data.jpa.repository.JpaRepository; public interface PlaceTodoRepository extends JpaRepository { - - List findByTravelPlanPlace(TravelPlanPlace travelPlanPlace); - - void deleteByTravelPlanPlaceDayPlan(TravelPlan travelPlan); } diff --git a/backend/src/main/java/kr/touroot/travelplan/repository/TravelPlanDayRepository.java b/backend/src/main/java/kr/touroot/travelplan/repository/TravelPlanDayRepository.java index d91444af..ac32c420 100644 --- a/backend/src/main/java/kr/touroot/travelplan/repository/TravelPlanDayRepository.java +++ b/backend/src/main/java/kr/touroot/travelplan/repository/TravelPlanDayRepository.java @@ -1,13 +1,7 @@ package kr.touroot.travelplan.repository; -import java.util.List; -import kr.touroot.travelplan.domain.TravelPlan; import kr.touroot.travelplan.domain.TravelPlanDay; import org.springframework.data.jpa.repository.JpaRepository; public interface TravelPlanDayRepository extends JpaRepository { - - List findByPlan(TravelPlan travelPlan); - - void deleteByPlan(TravelPlan plan); } diff --git a/backend/src/main/java/kr/touroot/travelplan/repository/TravelPlanPlaceRepository.java b/backend/src/main/java/kr/touroot/travelplan/repository/TravelPlanPlaceRepository.java index 36f07fc9..b61c6dcc 100644 --- a/backend/src/main/java/kr/touroot/travelplan/repository/TravelPlanPlaceRepository.java +++ b/backend/src/main/java/kr/touroot/travelplan/repository/TravelPlanPlaceRepository.java @@ -1,14 +1,7 @@ package kr.touroot.travelplan.repository; -import java.util.List; -import kr.touroot.travelplan.domain.TravelPlan; -import kr.touroot.travelplan.domain.TravelPlanDay; import kr.touroot.travelplan.domain.TravelPlanPlace; import org.springframework.data.jpa.repository.JpaRepository; public interface TravelPlanPlaceRepository extends JpaRepository { - - List findByDay(TravelPlanDay day); - - void deleteByDayPlan(TravelPlan plan); } diff --git a/backend/src/main/java/kr/touroot/travelplan/service/TravelPlanFacadeService.java b/backend/src/main/java/kr/touroot/travelplan/service/TravelPlanFacadeService.java new file mode 100644 index 00000000..4d3129e9 --- /dev/null +++ b/backend/src/main/java/kr/touroot/travelplan/service/TravelPlanFacadeService.java @@ -0,0 +1,60 @@ +package kr.touroot.travelplan.service; + +import java.util.UUID; +import kr.touroot.global.auth.dto.MemberAuth; +import kr.touroot.member.domain.Member; +import kr.touroot.member.service.MemberService; +import kr.touroot.travelplan.domain.TravelPlan; +import kr.touroot.travelplan.dto.request.PlanRequest; +import kr.touroot.travelplan.dto.response.PlanCreateResponse; +import kr.touroot.travelplan.dto.response.PlanResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Service +public class TravelPlanFacadeService { + + private final MemberService memberService; + private final TravelPlanService travelPlanService; + + @Transactional + public PlanCreateResponse createTravelPlan(PlanRequest request, MemberAuth memberAuth) { + Member author = memberService.getById(memberAuth.memberId()); + TravelPlan travelPlan = request.toTravelPlan(author, UUID.randomUUID()); + TravelPlan savedTravelPlan = travelPlanService.save(travelPlan); + + return PlanCreateResponse.from(savedTravelPlan); + } + + @Transactional(readOnly = true) + public PlanResponse findTravelPlanById(Long planId, MemberAuth memberAuth) { + Member accessor = memberService.getById(memberAuth.memberId()); + TravelPlan travelPlan = travelPlanService.getTravelPlanById(planId, accessor); + + return PlanResponse.from(travelPlan); + } + + @Transactional(readOnly = true) + public PlanResponse findTravelPlanByShareKey(UUID shareKey) { + return PlanResponse.from(travelPlanService.getTravelPlanByShareKey(shareKey)); + } + + @Transactional + public PlanResponse updateTravelPlanById(Long id, MemberAuth memberAuth, PlanRequest planUpdateRequest) { + Member accessor = memberService.getById(memberAuth.memberId()); + TravelPlan travelPlan = travelPlanService.getTravelPlanById(id, accessor); + TravelPlan updated = travelPlanService.updateTravelPlan(travelPlan, accessor, planUpdateRequest); + + return PlanResponse.from(updated); + } + + @Transactional + public void deleteTravelPlanById(Long id, MemberAuth memberAuth) { + Member accessor = memberService.getById(memberAuth.memberId()); + TravelPlan travelPlan = travelPlanService.getTravelPlanById(id, accessor); + + travelPlanService.deleteTravelPlan(travelPlan, accessor); + } +} diff --git a/backend/src/main/java/kr/touroot/travelplan/service/TravelPlanService.java b/backend/src/main/java/kr/touroot/travelplan/service/TravelPlanService.java index e0d422a2..a5334880 100644 --- a/backend/src/main/java/kr/touroot/travelplan/service/TravelPlanService.java +++ b/backend/src/main/java/kr/touroot/travelplan/service/TravelPlanService.java @@ -1,30 +1,12 @@ package kr.touroot.travelplan.service; import java.time.LocalDate; -import java.util.Comparator; -import java.util.List; import java.util.UUID; -import kr.touroot.global.auth.dto.MemberAuth; import kr.touroot.global.exception.BadRequestException; import kr.touroot.global.exception.ForbiddenException; import kr.touroot.member.domain.Member; -import kr.touroot.member.repository.MemberRepository; -import kr.touroot.travelplan.domain.TravelPlaceTodo; import kr.touroot.travelplan.domain.TravelPlan; -import kr.touroot.travelplan.domain.TravelPlanDay; -import kr.touroot.travelplan.domain.TravelPlanPlace; -import kr.touroot.travelplan.dto.request.PlanDayRequest; -import kr.touroot.travelplan.dto.request.PlanPlaceRequest; -import kr.touroot.travelplan.dto.request.PlanPlaceTodoRequest; import kr.touroot.travelplan.dto.request.PlanRequest; -import kr.touroot.travelplan.dto.response.PlanCreateResponse; -import kr.touroot.travelplan.dto.response.PlanDayResponse; -import kr.touroot.travelplan.dto.response.PlanPlaceResponse; -import kr.touroot.travelplan.dto.response.PlanPlaceTodoResponse; -import kr.touroot.travelplan.dto.response.PlanResponse; -import kr.touroot.travelplan.repository.PlaceTodoRepository; -import kr.touroot.travelplan.repository.TravelPlanDayRepository; -import kr.touroot.travelplan.repository.TravelPlanPlaceRepository; import kr.touroot.travelplan.repository.TravelPlanRepository; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; @@ -36,177 +18,58 @@ @Service public class TravelPlanService { - private final MemberRepository memberRepository; private final TravelPlanRepository travelPlanRepository; - private final TravelPlanDayRepository travelPlanDayRepository; - private final TravelPlanPlaceRepository travelPlanPlaceRepository; - private final PlaceTodoRepository placeTodoRepository; @Transactional - public PlanCreateResponse createTravelPlan(PlanRequest request, MemberAuth memberAuth) { - Member author = getMemberByMemberAuth(memberAuth); - TravelPlan travelPlan = request.toTravelPlan(author, UUID.randomUUID()); - validateCreateTravelPlan(travelPlan); - - TravelPlan savedTravelPlan = travelPlanRepository.save(travelPlan); - createPlanDay(request.days(), savedTravelPlan); - - return new PlanCreateResponse(savedTravelPlan.getId()); + public TravelPlan save(TravelPlan travelPlan) { + validateTravelPlanStartDate(travelPlan); + return travelPlanRepository.save(travelPlan); } - private void validateCreateTravelPlan(TravelPlan travelPlan) { + private void validateTravelPlanStartDate(TravelPlan travelPlan) { if (travelPlan.isStartDateBefore(LocalDate.now())) { throw new BadRequestException("지난 날짜에 대한 계획은 작성할 수 없습니다."); } } - private Member getMemberByMemberAuth(MemberAuth memberAuth) { - return memberRepository.findById(memberAuth.memberId()) - .orElseThrow(() -> new BadRequestException("존재하지 않는 사용자입니다.")); - } - - private void createPlanDay(List request, TravelPlan savedTravelPlan) { - for (int order = 0; order < request.size(); order++) { - PlanDayRequest dayRequest = request.get(order); - TravelPlanDay travelPlanDay = travelPlanDayRepository.save(dayRequest.toPlanDay(order, savedTravelPlan)); - createPlanPlace(dayRequest.places(), travelPlanDay); - } - } - - private void createPlanPlace(List request, TravelPlanDay travelPlanDay) { - for (int order = 0; order < request.size(); order++) { - PlanPlaceRequest planRequest = request.get(order); - TravelPlanPlace planPlace = planRequest.toPlanPlace(order, travelPlanDay); - TravelPlanPlace travelPlanPlace = travelPlanPlaceRepository.save(planPlace); - createPlaceTodo(planRequest.todos(), travelPlanPlace); - } - } - - private void createPlaceTodo(List request, TravelPlanPlace travelPlanPlace) { - for (int order = 0; order < request.size(); order++) { - PlanPlaceTodoRequest todoRequest = request.get(order); - TravelPlaceTodo travelPlaceTodo = todoRequest.toPlaceTodo(travelPlanPlace, order); - placeTodoRepository.save(travelPlaceTodo); - } - } - @Transactional(readOnly = true) - public PlanResponse readTravelPlan(Long planId, MemberAuth memberAuth) { - TravelPlan travelPlan = getTravelPlanById(planId); - Member member = getMemberByMemberAuth(memberAuth); - validateReadByAuthor(travelPlan, member); + public TravelPlan getTravelPlanById(Long planId, Member accessor) { + TravelPlan travelPlan = travelPlanRepository.findById(planId) + .orElseThrow(() -> new BadRequestException("존재하지 않는 여행 계획입니다.")); + validateAccessFromAuthor(travelPlan, accessor); - return PlanResponse.of(travelPlan, getTravelPlanDayResponses(travelPlan)); + return travelPlan; } - private void validateReadByAuthor(TravelPlan travelPlan, Member member) { + private void validateAccessFromAuthor(TravelPlan travelPlan, Member member) { if (!travelPlan.isAuthor(member)) { - throw new ForbiddenException("여행 계획 조회는 작성자만 가능합니다."); + throw new ForbiddenException("여행 계획은 작성자만 접근 가능합니다."); } } @Transactional(readOnly = true) - public PlanResponse readTravelPlan(UUID shareKey) { - TravelPlan travelPlan = getTravelPlanByShareKey(shareKey); - - return PlanResponse.of(travelPlan, getTravelPlanDayResponses(travelPlan)); - } - - private TravelPlan getTravelPlanById(Long planId) { - return travelPlanRepository.findById(planId) - .orElseThrow(() -> new BadRequestException("존재하지 않는 여행 계획입니다.")); - } - - private TravelPlan getTravelPlanByShareKey(UUID shareKey) { + public TravelPlan getTravelPlanByShareKey(UUID shareKey) { return travelPlanRepository.findByShareKey(shareKey) .orElseThrow(() -> new BadRequestException("존재하지 않는 여행 계획입니다.")); } - public PlanResponse getTravelPlanResponse(TravelPlan travelPlan) { - return PlanResponse.of(travelPlan, getTravelPlanDayResponses(travelPlan)); - } - - private List getTravelPlanDayResponses(TravelPlan travelPlan) { - List planDays = travelPlanDayRepository.findByPlan(travelPlan); - - return planDays.stream() - .sorted(Comparator.comparing(TravelPlanDay::getOrder)) - .map(day -> PlanDayResponse.of(day, getTravelPlanPlaceResponses(day))) - .toList(); - } - - private List getTravelPlanPlaceResponses(TravelPlanDay day) { - List places = travelPlanPlaceRepository.findByDay(day); - - return places.stream() - .sorted(Comparator.comparing(TravelPlanPlace::getOrder)) - .map(place -> PlanPlaceResponse.of(place, getPlaceTodos(place))) - .toList(); - } - - private List getPlaceTodos(TravelPlanPlace place) { - return placeTodoRepository.findByTravelPlanPlace(place) - .stream() - .sorted(Comparator.comparing(TravelPlaceTodo::getOrder)) - .map(PlanPlaceTodoResponse::from) - .toList(); - } - @Transactional(readOnly = true) public Page getAllByAuthor(Member member, Pageable pageable) { return travelPlanRepository.findAllByAuthor(member, pageable); } - @Transactional(readOnly = true) - public int calculateTravelPeriod(TravelPlan travelPlan) { - return travelPlanDayRepository.findByPlan(travelPlan) - .size(); - } @Transactional - public PlanCreateResponse updateTravelPlan(Long planId, MemberAuth memberAuth, PlanRequest request) { - TravelPlan travelPlan = getTravelPlanById(planId); - Member author = getMemberByMemberAuth(memberAuth); - validateUpdateByAuthor(travelPlan, author); - - clearTravelPlanContents(travelPlan); - updateTravelPlanContents(request, travelPlan); - return new PlanCreateResponse(travelPlan.getId()); - } - - private void validateUpdateByAuthor(TravelPlan travelPlan, Member member) { - if (!travelPlan.isAuthor(member)) { - throw new ForbiddenException("여행 계획 수정은 작성자만 가능합니다."); - } - } - - private void clearTravelPlanContents(TravelPlan travelPlan) { - placeTodoRepository.deleteByTravelPlanPlaceDayPlan(travelPlan); - travelPlanPlaceRepository.deleteByDayPlan(travelPlan); - travelPlanDayRepository.deleteByPlan(travelPlan); - } - - private void updateTravelPlanContents(PlanRequest request, TravelPlan travelPlan) { - travelPlan.update(request.title(), request.startDate()); - travelPlanRepository.save(travelPlan); - createPlanDay(request.days(), travelPlan); + public TravelPlan updateTravelPlan(TravelPlan travelPlan, Member member, PlanRequest updateRequest) { + validateAccessFromAuthor(travelPlan, member); + travelPlan.updateDays(updateRequest.getDays(travelPlan)); + travelPlan.update(updateRequest.title(), updateRequest.startDate()); + return travelPlanRepository.save(travelPlan); } @Transactional - public void deleteByTravelPlanId(Long planId, MemberAuth memberAuth) { - TravelPlan travelPlan = getTravelPlanById(planId); - Member author = getMemberByMemberAuth(memberAuth); - validateDeleteByAuthor(travelPlan, author); - - placeTodoRepository.deleteByTravelPlanPlaceDayPlan(travelPlan); - travelPlanPlaceRepository.deleteByDayPlan(travelPlan); - travelPlanDayRepository.deleteByPlan(travelPlan); + public void deleteTravelPlan(TravelPlan travelPlan, Member author) { + validateAccessFromAuthor(travelPlan, author); travelPlanRepository.delete(travelPlan); } - - private void validateDeleteByAuthor(TravelPlan travelPlan, Member member) { - if (!travelPlan.isAuthor(member)) { - throw new ForbiddenException("여행 계획 삭제는 작성자만 가능합니다."); - } - } } diff --git a/backend/src/test/java/kr/touroot/travelplan/controller/TravelPlanControllerTest.java b/backend/src/test/java/kr/touroot/travelplan/controller/TravelPlanControllerTest.java index d0b59686..a6657172 100644 --- a/backend/src/test/java/kr/touroot/travelplan/controller/TravelPlanControllerTest.java +++ b/backend/src/test/java/kr/touroot/travelplan/controller/TravelPlanControllerTest.java @@ -172,7 +172,7 @@ void readTravelPlanWithNotAuthor() { .get("/api/v1/travel-plans/" + id) .then().log().all() .statusCode(403) - .body("message", is("여행 계획 조회는 작성자만 가능합니다.")); + .body("message", is("여행 계획은 작성자만 접근 가능합니다.")); } @DisplayName("여행 계획 공유 키를 통해 여행 계획을 조회할 수 있다") @@ -333,7 +333,7 @@ void updateTravelPlanWithNotAuthor() { .when().put("/api/v1/travel-plans/" + id) .then().log().all() .statusCode(403) - .body("message", is("여행 계획 수정은 작성자만 가능합니다.")); + .body("message", is("여행 계획은 작성자만 접근 가능합니다.")); } @DisplayName("여행계획을 삭제한다.") @@ -373,6 +373,6 @@ void deleteTravelPlanWithNotAuthor() { .when().delete("/api/v1/travel-plans/" + id) .then().log().all() .statusCode(403) - .body("message", is("여행 계획 삭제는 작성자만 가능합니다.")); + .body("message", is("여행 계획은 작성자만 접근 가능합니다.")); } } diff --git a/backend/src/test/java/kr/touroot/travelplan/domain/TravelPlanDayTest.java b/backend/src/test/java/kr/touroot/travelplan/domain/TravelPlanDayTest.java index 4f0fc954..5de3150c 100644 --- a/backend/src/test/java/kr/touroot/travelplan/domain/TravelPlanDayTest.java +++ b/backend/src/test/java/kr/touroot/travelplan/domain/TravelPlanDayTest.java @@ -1,10 +1,13 @@ package kr.touroot.travelplan.domain; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; import kr.touroot.global.exception.BadRequestException; +import kr.touroot.travelplan.fixture.TravelPlanDayFixture; import kr.touroot.travelplan.fixture.TravelPlanFixture; +import kr.touroot.travelplan.fixture.TravelPlanPlaceFixture; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -47,4 +50,26 @@ void createTravelPlanDayWithNegativeOrder(int negative) { .isInstanceOf(BadRequestException.class) .hasMessage("여행 계획 날짜 순서는 음수일 수 없습니다"); } + + @DisplayName("여행 계획 장소를 추가할 수 있다") + @Test + void addTravelPlanPlaceInTravelPlanDay() { + TravelPlanDay travelPlanDay = TravelPlanDayFixture.TRAVEL_PLAN_DAY.get(); + TravelPlanPlace travelPlanPlace = TravelPlanPlaceFixture.TRAVEL_PLAN_PLACE.get(); + + travelPlanDay.addPlace(travelPlanPlace); + + assertThat(travelPlanDay.getTravelPlanPlaces()).containsExactly(travelPlanPlace); + } + + @DisplayName("여행 계획 날짜에 장소를 추가하는 경우 장소의 날짜 참조도 수정된다") + @Test + void addTravelPlanPlaceThenTravelPlanDayUpdated() { + TravelPlanDay travelPlanDay = TravelPlanDayFixture.TRAVEL_PLAN_DAY.get(); + TravelPlanPlace travelPlanPlace = TravelPlanPlaceFixture.TRAVEL_PLAN_PLACE.get(); + + travelPlanDay.addPlace(travelPlanPlace); + + assertThat(travelPlanPlace.getDay()).isEqualTo(travelPlanDay); + } } diff --git a/backend/src/test/java/kr/touroot/travelplan/domain/TravelPlanPlaceTest.java b/backend/src/test/java/kr/touroot/travelplan/domain/TravelPlanPlaceTest.java index 94de414e..b29f429e 100644 --- a/backend/src/test/java/kr/touroot/travelplan/domain/TravelPlanPlaceTest.java +++ b/backend/src/test/java/kr/touroot/travelplan/domain/TravelPlanPlaceTest.java @@ -1,10 +1,12 @@ package kr.touroot.travelplan.domain; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; import kr.touroot.global.exception.BadRequestException; import kr.touroot.travelplan.fixture.TravelPlanDayFixture; +import kr.touroot.travelplan.fixture.TravelPlanPlaceFixture; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -76,4 +78,26 @@ void createPlaceWithInvalidLengthPlaceName() { .isInstanceOf(BadRequestException.class) .hasMessage("장소 이름은 60자 이하여야 합니다"); } + + @DisplayName("Todo를 추가할 수 있다") + @Test + void addTodoInPlace() { + TravelPlanPlace travelPlanPlace = TravelPlanPlaceFixture.TRAVEL_PLAN_PLACE.get(); + TravelPlaceTodo travelPlaceTodo = new TravelPlaceTodo(travelPlanPlace, "투룻 하기", 1, true); + + travelPlanPlace.addTodo(travelPlaceTodo); + + assertThat(travelPlanPlace.getTravelPlaceTodos()).containsExactly(travelPlaceTodo); + } + + @DisplayName("장소에 Todo를 추가하면 Todo의 장소 참조도 수정된다") + @Test + void addTodoThenTodosPlaceUpdated() { + TravelPlanPlace travelPlanPlace = TravelPlanPlaceFixture.TRAVEL_PLAN_PLACE.get(); + TravelPlaceTodo travelPlaceTodo = new TravelPlaceTodo(travelPlanPlace, "투룻 하기", 1, true); + + travelPlanPlace.addTodo(travelPlaceTodo); + + assertThat(travelPlaceTodo.getTravelPlanPlace()).isEqualTo(travelPlanPlace); + } } diff --git a/backend/src/test/java/kr/touroot/travelplan/domain/TravelPlanTest.java b/backend/src/test/java/kr/touroot/travelplan/domain/TravelPlanTest.java index 6d974348..0c8f5c4e 100644 --- a/backend/src/test/java/kr/touroot/travelplan/domain/TravelPlanTest.java +++ b/backend/src/test/java/kr/touroot/travelplan/domain/TravelPlanTest.java @@ -6,11 +6,13 @@ import static org.junit.jupiter.api.Assertions.assertAll; import java.time.LocalDate; +import java.util.List; import java.util.UUID; import kr.touroot.global.exception.BadRequestException; import kr.touroot.member.domain.LoginType; import kr.touroot.member.domain.Member; import kr.touroot.member.fixture.MemberFixture; +import kr.touroot.travelplan.fixture.TravelPlanDayFixture; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -107,4 +109,46 @@ void validateAuthor() { // then assertThat(actual).isFalse(); } + + @DisplayName("여행 계획 날짜를 추가할 수 있다") + @Test + void addDayInTravelPlan() { + // given + TravelPlan travelPlan = new TravelPlan(VALID_TITLE, VALID_START_DATE, VALID_UUID, VALID_AUTHOR); + TravelPlanDay travelPlanDay = TravelPlanDayFixture.TRAVEL_PLAN_DAY.get(); + + // when + travelPlan.addDay(travelPlanDay); + + // then + assertThat(travelPlan.getTravelPlanDays()).containsExactly(travelPlanDay); + } + + @DisplayName("여행 계획에 여행 계획 날짜를 추가하면 계획 날짜의 여행 계획 참조도 수정된다") + @Test + void addDayThenDaysPlanUpdated() { + // given + TravelPlan travelPlan = new TravelPlan(VALID_TITLE, VALID_START_DATE, VALID_UUID, VALID_AUTHOR); + TravelPlanDay travelPlanDay = TravelPlanDayFixture.TRAVEL_PLAN_DAY.get(); + + // when + travelPlan.addDay(travelPlanDay); + + // then + assertThat(travelPlanDay.getPlan()).isEqualTo(travelPlan); + } + + @DisplayName("여행 계획의 날짜들을 새로운 날짜들로 업데이트 할 수 있다") + @Test + void updateDaysInTravelPlan() { + // given + TravelPlan travelPlan = new TravelPlan(VALID_TITLE, VALID_START_DATE, VALID_UUID, VALID_AUTHOR); + TravelPlanDay travelPlanDay = TravelPlanDayFixture.TRAVEL_PLAN_DAY.get(); + + // when + travelPlan.updateDays(List.of(travelPlanDay)); + + // then + assertThat(travelPlan.getTravelPlanDays()).containsExactly(travelPlanDay); + } } diff --git a/backend/src/test/java/kr/touroot/travelplan/fixture/TravelPlanFixture.java b/backend/src/test/java/kr/touroot/travelplan/fixture/TravelPlanFixture.java index 9a35c57b..14b51294 100644 --- a/backend/src/test/java/kr/touroot/travelplan/fixture/TravelPlanFixture.java +++ b/backend/src/test/java/kr/touroot/travelplan/fixture/TravelPlanFixture.java @@ -11,13 +11,22 @@ @AllArgsConstructor public enum TravelPlanFixture { - TRAVEL_PLAN("제주도 여행 계획", LocalDate.now().plusDays(2), MemberFixture.KAKAO_MEMBER.build()); + TRAVEL_PLAN("제주도 여행 계획", LocalDate.now().plusDays(2), UUID.randomUUID(), MemberFixture.KAKAO_MEMBER.build()); private final String title; private final LocalDate startDate; + private final UUID shareKey; private final Member author; public TravelPlan get() { - return new TravelPlan(title, startDate, UUID.randomUUID(), author); + return new TravelPlan(title, startDate, shareKey, author); + } + + public TravelPlan get(Member author) { + return new TravelPlan(title, startDate, shareKey, author); + } + + public TravelPlan get(Member author, LocalDate startDate) { + return new TravelPlan(title, startDate, shareKey, author); } } diff --git a/backend/src/test/java/kr/touroot/travelplan/service/TravelPlanFacadeServiceTest.java b/backend/src/test/java/kr/touroot/travelplan/service/TravelPlanFacadeServiceTest.java new file mode 100644 index 00000000..b0372da0 --- /dev/null +++ b/backend/src/test/java/kr/touroot/travelplan/service/TravelPlanFacadeServiceTest.java @@ -0,0 +1,160 @@ +package kr.touroot.travelplan.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.time.LocalDate; +import java.util.Collections; +import java.util.List; +import kr.touroot.authentication.infrastructure.PasswordEncryptor; +import kr.touroot.global.ServiceTest; +import kr.touroot.global.auth.dto.MemberAuth; +import kr.touroot.global.exception.BadRequestException; +import kr.touroot.member.domain.Member; +import kr.touroot.member.service.MemberService; +import kr.touroot.travelplan.domain.TravelPlan; +import kr.touroot.travelplan.dto.request.PlanDayRequest; +import kr.touroot.travelplan.dto.request.PlanPlaceRequest; +import kr.touroot.travelplan.dto.request.PlanPositionRequest; +import kr.touroot.travelplan.dto.request.PlanRequest; +import kr.touroot.travelplan.dto.response.PlanCreateResponse; +import kr.touroot.travelplan.dto.response.PlanResponse; +import kr.touroot.travelplan.helper.TravelPlanTestHelper; +import kr.touroot.utils.DatabaseCleaner; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Import; + +@DisplayName("여행 계획 파사드 서비스 테스트") +@Import({ + TravelPlanFacadeService.class, + TravelPlanService.class, + MemberService.class, + PasswordEncryptor.class, + TravelPlanTestHelper.class +}) +@ServiceTest +class TravelPlanFacadeServiceTest { + + private final TravelPlanFacadeService travelPlanFacadeService; + private final DatabaseCleaner databaseCleaner; + private final TravelPlanTestHelper testHelper; + + private MemberAuth memberAuth; + private Member author; + + @Autowired + public TravelPlanFacadeServiceTest( + TravelPlanFacadeService travelPlanFacadeService, + DatabaseCleaner databaseCleaner, + TravelPlanTestHelper testHelper + ) { + this.travelPlanFacadeService = travelPlanFacadeService; + this.databaseCleaner = databaseCleaner; + this.testHelper = testHelper; + } + + @BeforeEach + void setUp() { + databaseCleaner.executeTruncate(); + + author = testHelper.initMemberTestData(); + memberAuth = new MemberAuth(author.getId()); + } + + @DisplayName("여행 계획 생성 시 생성된 id를 응답한다.") + @Test + void createTravelPlan() { + // given + PlanPositionRequest locationRequest = new PlanPositionRequest("37.5175896", "127.0867236"); + PlanPlaceRequest planPlaceRequest = PlanPlaceRequest.builder() + .placeName("잠실한강공원") + .todos(Collections.EMPTY_LIST) + .position(locationRequest) + .build(); + PlanDayRequest planDayRequest = new PlanDayRequest(List.of(planPlaceRequest)); + PlanRequest request = PlanRequest.builder() + .title("신나는 한강 여행") + .startDate(LocalDate.MAX) + .days(List.of(planDayRequest)) + .build(); + + // when + PlanCreateResponse actual = travelPlanFacadeService.createTravelPlan(request, memberAuth); + + // then + assertThat(actual.id()).isEqualTo(1L); + } + + @DisplayName("여행 계획 상세 정보를 조회할 수 있다") + @Test + void readTravelPlan() { + // given + Long id = testHelper.initTravelPlanTestData(author).getId(); + + // when + PlanResponse actual = travelPlanFacadeService.findTravelPlanById(id, memberAuth); + + // then + assertThat(actual.id()).isEqualTo(id); + } + + @DisplayName("공유 키로 여행 계획을 조회할 수 있다") + @Test + void readTravelPlanByShareKey() { + // given + TravelPlan travelPlan = testHelper.initTravelPlanTestData(author); + + // when + PlanResponse actual = travelPlanFacadeService.findTravelPlanByShareKey(travelPlan.getShareKey()); + + // then + assertThat(actual.shareKey()).isEqualTo(travelPlan.getShareKey()); + } + + @DisplayName("새로운 정보로 여행 계획을 수정할 수 있다") + @Test + void updateTravelPlan() { + // given + TravelPlan travelPlan = testHelper.initTravelPlanTestData(author); + PlanPositionRequest locationRequest = new PlanPositionRequest("37.5175896", "127.0867236"); + PlanPlaceRequest planPlaceRequest = PlanPlaceRequest.builder() + .placeName("잠실한강공원") + .todos(Collections.EMPTY_LIST) + .position(locationRequest) + .build(); + PlanDayRequest planDayRequest = new PlanDayRequest(List.of(planPlaceRequest)); + PlanRequest request = PlanRequest.builder() + .title("수정된 한강 여행") + .startDate(LocalDate.MAX) + .days(List.of(planDayRequest)) + .build(); + + // when + PlanResponse updateResponse = travelPlanFacadeService.updateTravelPlanById( + travelPlan.getId(), + memberAuth, + request + ); + + // then + assertThat(updateResponse.title()).isEqualTo("수정된 한강 여행"); + } + + @DisplayName("여행 계획을 삭제할 수 있다") + @Test + void deleteTravelPlanById() { + // given + TravelPlan travelPlan = testHelper.initTravelPlanTestData(author); + + // when + travelPlanFacadeService.deleteTravelPlanById(travelPlan.getId(), memberAuth); + + // then + assertThatThrownBy(() -> travelPlanFacadeService.findTravelPlanById(travelPlan.getId(), memberAuth)) + .isInstanceOf(BadRequestException.class) + .hasMessage("존재하지 않는 여행 계획입니다."); + } +} diff --git a/backend/src/test/java/kr/touroot/travelplan/service/TravelPlanServiceTest.java b/backend/src/test/java/kr/touroot/travelplan/service/TravelPlanServiceTest.java index d591b6bd..598532da 100644 --- a/backend/src/test/java/kr/touroot/travelplan/service/TravelPlanServiceTest.java +++ b/backend/src/test/java/kr/touroot/travelplan/service/TravelPlanServiceTest.java @@ -18,8 +18,7 @@ import kr.touroot.travelplan.dto.request.PlanPlaceRequest; import kr.touroot.travelplan.dto.request.PlanPositionRequest; import kr.touroot.travelplan.dto.request.PlanRequest; -import kr.touroot.travelplan.dto.response.PlanCreateResponse; -import kr.touroot.travelplan.dto.response.PlanResponse; +import kr.touroot.travelplan.fixture.TravelPlanFixture; import kr.touroot.travelplan.helper.TravelPlanTestHelper; import kr.touroot.travelplan.repository.TravelPlanRepository; import kr.touroot.utils.DatabaseCleaner; @@ -63,49 +62,28 @@ void setUp() { memberAuth = new MemberAuth(author.getId()); } - @DisplayName("여행 계획 서비스는 여행 계획 생성 시 생성된 id를 응답한다.") + @DisplayName("여행 계획을 저장할 수 있다") @Test void createTravelPlan() { // given - PlanPositionRequest locationRequest = new PlanPositionRequest("37.5175896", "127.0867236"); - PlanPlaceRequest planPlaceRequest = PlanPlaceRequest.builder() - .placeName("잠실한강공원") - .todos(Collections.EMPTY_LIST) - .position(locationRequest) - .build(); - PlanDayRequest planDayRequest = new PlanDayRequest(List.of(planPlaceRequest)); - PlanRequest request = PlanRequest.builder() - .title("신나는 한강 여행") - .startDate(LocalDate.MAX) - .days(List.of(planDayRequest)) - .build(); + TravelPlan travelPlan = TravelPlanFixture.TRAVEL_PLAN.get(author); // when - PlanCreateResponse actual = travelPlanService.createTravelPlan(request, memberAuth); + TravelPlan actual = travelPlanService.save(travelPlan); // then - assertThat(actual.id()).isEqualTo(1L); + assertThat(actual.getId()).isEqualTo(1L); } - @DisplayName("여행 계획 서비스는 지난 날짜로 여행 계획 생성 시 예외를 반환한다.") + @DisplayName("지난 날짜로 여행 계획 저장 시 예외를 반환한다.") @Test void createTravelPlanWithInvalidStartDate() { // given - PlanPositionRequest locationRequest = new PlanPositionRequest("37.5175896", "127.0867236"); - PlanPlaceRequest planPlaceRequest = PlanPlaceRequest.builder() - .placeName("잠실한강공원") - .position(locationRequest) - .todos(Collections.EMPTY_LIST) - .build(); - PlanDayRequest planDayRequest = new PlanDayRequest(List.of(planPlaceRequest)); - PlanRequest request = PlanRequest.builder() - .title("신나는 한강 여행") - .startDate(LocalDate.MIN) - .days(List.of(planDayRequest)) - .build(); + LocalDate past = LocalDate.now().minusDays(1); + TravelPlan travelPlan = TravelPlanFixture.TRAVEL_PLAN.get(author, past); - // when & then= - assertThatThrownBy(() -> travelPlanService.createTravelPlan(request, memberAuth)) + // when & then + assertThatThrownBy(() -> travelPlanService.save(travelPlan)) .isInstanceOf(BadRequestException.class) .hasMessage("지난 날짜에 대한 계획은 작성할 수 없습니다."); } @@ -113,78 +91,53 @@ void createTravelPlanWithInvalidStartDate() { @DisplayName("당일에 시작하는 여행 계획을 생성할 수 있다") @Test void createTravelPlanStartsAtToday() { - // given - PlanPositionRequest locationRequest = new PlanPositionRequest("37.5175896", "127.0867236"); - PlanPlaceRequest planPlaceRequest = PlanPlaceRequest.builder() - .placeName("잠실한강공원") - .todos(Collections.EMPTY_LIST) - .position(locationRequest) - .build(); - PlanDayRequest planDayRequest = new PlanDayRequest(List.of(planPlaceRequest)); - PlanRequest request = PlanRequest.builder() - .title("신나는 한강 여행") - .startDate(LocalDate.now()) - .days(List.of(planDayRequest)) - .build(); + LocalDate today = LocalDate.now(); + TravelPlan travelPlan = TravelPlanFixture.TRAVEL_PLAN.get(author, today); // when & then= - assertThatCode(() -> travelPlanService.createTravelPlan(request, memberAuth)) + assertThatCode(() -> travelPlanService.save(travelPlan)) .doesNotThrowAnyException(); } - @DisplayName("여행 계획 서비스는 여행 계획 조회 시 상세 정보를 반환한다.") + @DisplayName("여행 계획을 가져올 수 있다") @Test void readTravelPlan() { // given Long id = testHelper.initTravelPlanTestData(author).getId(); // when - PlanResponse actual = travelPlanService.readTravelPlan(id, memberAuth); + TravelPlan actual = travelPlanService.getTravelPlanById(id, author); // then - assertThat(actual.id()).isEqualTo(id); + assertThat(actual.getId()).isEqualTo(id); } - @DisplayName("여행 계획 서비스는 존재하지 않는 여행 계획 조회 시 예외를 반환한다.") + @DisplayName("존재하지 않는 여행 계획을 가져오려고 할 경우 예외를 반환한다.") @Test void readTravelPlanWitNonExist() { // given - databaseCleaner.executeTruncate(); - Long id = 1L; + Long noExistId = 1L; // when & then - assertThatThrownBy(() -> travelPlanService.readTravelPlan(id, memberAuth)) + assertThatThrownBy(() -> travelPlanService.getTravelPlanById(noExistId, author)) .isInstanceOf(BadRequestException.class) .hasMessage("존재하지 않는 여행 계획입니다."); } - @DisplayName("여행 계획 서비스는 작성자가 아닌 사용자가 조회 시 예외를 반환한다.") + @DisplayName("작성자가 아닌 사용자가 여행 계획을 가져오려고 하는 경우 예외를 반환한다.") @Test void readTravelPlanWithNotAuthor() { // given Long id = testHelper.initTravelPlanTestData(author).getId(); - MemberAuth notAuthor = new MemberAuth(testHelper.initMemberTestData().getId()); + Member notAuthor = testHelper.initMemberTestData(); // when & then - assertThatThrownBy(() -> travelPlanService.readTravelPlan(id, notAuthor)) + assertThatThrownBy(() -> travelPlanService.getTravelPlanById(id, notAuthor)) .isInstanceOf(ForbiddenException.class) - .hasMessage("여행 계획 조회는 작성자만 가능합니다."); + .hasMessage("여행 계획은 작성자만 접근 가능합니다."); } - @DisplayName("여행 계획 서비스는 여행 계획 일자를 계산해 반환한다.") - @Test - void calculateTravelPeriod() { - // given - TravelPlan travelPlan = testHelper.initTravelPlanTestData(author); - - // when - int actual = travelPlanService.calculateTravelPeriod(travelPlan); - - // then - assertThat(actual).isEqualTo(1); - } - - @DisplayName("여행 계획 서비스는 새로운 정보로 여행 계획을 수정한다.") + @DisplayName("새로운 정보로 여행 계획을 수정할 수 있다") @Test void updateTravelPlan() { // given @@ -197,48 +150,26 @@ void updateTravelPlan() { .build(); PlanDayRequest planDayRequest = new PlanDayRequest(List.of(planPlaceRequest)); PlanRequest request = PlanRequest.builder() - .title("신나는 한강 여행") + .title("수정된 한강 여행") .startDate(LocalDate.MAX) .days(List.of(planDayRequest)) .build(); // when - PlanCreateResponse updatedTravelPlan = travelPlanService.updateTravelPlan(travelPlan.getId(), memberAuth, - request); + travelPlanService.updateTravelPlan(travelPlan, author, request); + TravelPlan updated = travelPlanService.getTravelPlanById(travelPlan.getId(), author); // then - assertThat(updatedTravelPlan.id()).isEqualTo(1L); - } - - @DisplayName("여행 계획 서비스는 존재하지 않는 여행 계획 수정 시 예외를 반환한다.") - @Test - void updateTravelPlanWitNonExist() { - // given - PlanPositionRequest locationRequest = new PlanPositionRequest("37.5175896", "127.0867236"); - PlanPlaceRequest planPlaceRequest = PlanPlaceRequest.builder() - .placeName("잠실한강공원") - .todos(Collections.EMPTY_LIST) - .position(locationRequest) - .build(); - PlanDayRequest planDayRequest = new PlanDayRequest(List.of(planPlaceRequest)); - PlanRequest request = PlanRequest.builder() - .title("신나는 한강 여행") - .startDate(LocalDate.MAX) - .days(List.of(planDayRequest)) - .build(); - - // when & then - assertThatThrownBy(() -> travelPlanService.updateTravelPlan(1L, memberAuth, request)) - .isInstanceOf(BadRequestException.class) - .hasMessage("존재하지 않는 여행 계획입니다."); + assertThat(updated.getTitle()).isEqualTo("수정된 한강 여행"); } - @DisplayName("여행 계획 서비스는 작성자가 아닌 사용자가 수정 시 예외를 반환한다.") + @DisplayName("작성자가 아닌 사용자가 여행 계획 수정 시도 시 예외를 반환한다.") @Test void updateTravelPlanWithNotAuthor() { // given - Long id = testHelper.initTravelPlanTestData(author).getId(); - MemberAuth notAuthor = new MemberAuth(testHelper.initMemberTestData().getId()); + TravelPlan travelPlan = testHelper.initTravelPlanTestData(author); + Member notAuthor = testHelper.initMemberTestData(); + PlanPositionRequest locationRequest = new PlanPositionRequest("37.5175896", "127.0867236"); PlanPlaceRequest planPlaceRequest = PlanPlaceRequest.builder() .placeName("잠실한강공원") @@ -253,58 +184,48 @@ void updateTravelPlanWithNotAuthor() { .build(); // when & then - assertThatThrownBy(() -> travelPlanService.updateTravelPlan(id, notAuthor, request)) + assertThatThrownBy(() -> travelPlanService.updateTravelPlan(travelPlan, notAuthor, request)) .isInstanceOf(ForbiddenException.class) - .hasMessage("여행 계획 수정은 작성자만 가능합니다."); + .hasMessage("여행 계획은 작성자만 접근 가능합니다."); } - @DisplayName("여행계획을 ID 기준으로 삭제할 수 있다.") + @DisplayName("여행 계획을 삭제할 수 있다.") @Test void deleteTravelPlanById() { + // given TravelPlan travelPlan = testHelper.initTravelPlanTestData(author); - travelPlanService.deleteByTravelPlanId(travelPlan.getId(), memberAuth); + + // when + travelPlanService.deleteTravelPlan(travelPlan, author); assertThat(travelPlanRepository.findById(travelPlan.getId())) .isEmpty(); } - @DisplayName("여행 계획 서비스는 존재하지 않는 여행 계획 삭제 시 예외를 반환한다.") - @Test - void deleteTravelPlanWitNonExist() { - // given - databaseCleaner.executeTruncate(); - Long id = 1L; - - // when & then - assertThatThrownBy(() -> travelPlanService.deleteByTravelPlanId(id, memberAuth)) - .isInstanceOf(BadRequestException.class) - .hasMessage("존재하지 않는 여행 계획입니다."); - } - - @DisplayName("여행 계획 서비스는 작성자가 아닌 사용자가 삭제 시 예외를 반환한다.") + @DisplayName("작성자가 아닌 사용자가 여행 계획 삭제 시 예외를 반환한다.") @Test void deleteTravelPlanWithNotAuthor() { // given - Long id = testHelper.initTravelPlanTestData(author).getId(); - MemberAuth notAuthor = new MemberAuth(testHelper.initMemberTestData().getId()); + TravelPlan travelPlan = testHelper.initTravelPlanTestData(author); + Member notAuthor = testHelper.initMemberTestData(); // when & then - assertThatThrownBy(() -> travelPlanService.deleteByTravelPlanId(id, notAuthor)) + assertThatThrownBy(() -> travelPlanService.deleteTravelPlan(travelPlan, notAuthor)) .isInstanceOf(ForbiddenException.class) - .hasMessage("여행 계획 삭제는 작성자만 가능합니다."); + .hasMessage("여행 계획은 작성자만 접근 가능합니다."); } - @DisplayName("여행 계획 서비스는 공유 키로 여행 계획을 조회할 수 있다") + @DisplayName("공유 키로 여행 계획을 가져올 수 있다") @Test void readTravelPlanByShareKey() { // given TravelPlan travelPlan = testHelper.initTravelPlanTestData(author); // when - PlanResponse actual = travelPlanService.readTravelPlan(travelPlan.getShareKey()); + TravelPlan actual = travelPlanService.getTravelPlanByShareKey(travelPlan.getShareKey()); // then - assertThat(actual.shareKey()).isEqualTo(travelPlan.getShareKey()); + assertThat(actual.getShareKey()).isEqualTo(travelPlan.getShareKey()); } @DisplayName("여행 계획 서비스는 존재하지 않는 공유 키로 여행 계획을 조회할 경우 예외가 발생한다") @@ -314,7 +235,7 @@ void readTravelPlanByInvalidShareKey() { TravelPlan travelPlan = testHelper.initTravelPlanTestData(author); // when & then - assertThatThrownBy(() -> travelPlanService.readTravelPlan(UUID.randomUUID())) + assertThatThrownBy(() -> travelPlanService.getTravelPlanByShareKey(UUID.randomUUID())) .isInstanceOf(BadRequestException.class) .hasMessage("존재하지 않는 여행 계획입니다."); }