From 972ac86d4a1bc20343ef0a6767456d44d5cad472 Mon Sep 17 00:00:00 2001 From: junhokim Date: Sun, 27 Jul 2025 18:26:38 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=201=EC=B0=A8=20=EB=A6=AC=ED=8E=99?= =?UTF-8?q?=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 수정 필요 --- .../in/request/TripCreateRequest.java | 52 +- .../in/response/MyTripResponse.java | 16 + .../in/response/TripCreateResponse.java | 84 ++- .../in/service/InvitationService.java | 33 +- .../in/{ => service}/ItineraryService.java | 24 +- .../in/service/ParticipantService.java | 62 ++ .../in/{ => service}/TripService.java | 60 +- .../in/usecase/GetTripUseCase.java | 6 +- .../in/usecase/ParticipantManageUseCase.java | 20 + .../ParticipantQueryRepository.java | 22 + .../out/repository/ParticipantRepository.java | 11 + .../repository/TripParticipantRepository.java | 9 - .../out/repository/TripQueryRepository.java | 12 +- .../com/retrip/trip/domain/entity/Trip.java | 129 ++-- .../retrip/trip/domain/entity/TripDemand.java | 17 +- .../trip/domain/entity/TripParticipants.java | 186 ------ .../Participant.java} | 47 +- .../entity/participant/Participants.java | 128 ++++ ...Exception.java => NotLeaderException.java} | 4 +- .../exception/ParticipantFullException.java | 16 + .../domain/exception/TripFullException.java | 10 - .../domain/exception/common/ErrorCode.java | 6 +- .../trip/domain/service/InvitationPolicy.java | 20 +- .../domain/service/ParticipantPolicy.java | 22 + .../in/presentation/rest/TripController.java | 38 +- .../query/ParticipantQuerydslRepository.java | 68 ++ .../mysql/query/TripQuerydslRepository.java | 121 ++-- .../in/ParticipantServiceTest.java | 65 ++ .../trip/application/in/TripServiceTest.java | 601 +++++++++--------- .../in/base/BaseItineraryServiceTest.java | 11 +- .../in/base/BaseParticipantServiceTest.java | 40 ++ .../in/base/BaseTripServiceTest.java | 36 +- .../in/request/TripRequestFixture.java | 4 +- .../trip/domain/entity/ItinerariesTest.java | 328 +++++----- .../domain/entity/ItineraryDetailsTest.java | 62 +- .../trip/domain/entity/ParticipantsTest.java | 100 +++ .../domain/entity/TripParticipantsTest.java | 120 ---- .../retrip/trip/domain/entity/TripTest.java | 146 ++--- .../domain/fixture/ParticipantFixture.java | 23 + .../trip/domain/fixture/TripFixture.java | 21 +- .../domain/service/InvitationPolicyTest.java | 35 +- .../domain/service/ParticipantPolicyTest.java | 39 ++ 42 files changed, 1583 insertions(+), 1271 deletions(-) create mode 100644 src/main/java/com/retrip/trip/application/in/response/MyTripResponse.java rename src/main/java/com/retrip/trip/application/in/{ => service}/ItineraryService.java (74%) create mode 100644 src/main/java/com/retrip/trip/application/in/service/ParticipantService.java rename src/main/java/com/retrip/trip/application/in/{ => service}/TripService.java (63%) create mode 100644 src/main/java/com/retrip/trip/application/in/usecase/ParticipantManageUseCase.java create mode 100644 src/main/java/com/retrip/trip/application/out/repository/ParticipantQueryRepository.java create mode 100644 src/main/java/com/retrip/trip/application/out/repository/ParticipantRepository.java delete mode 100644 src/main/java/com/retrip/trip/application/out/repository/TripParticipantRepository.java delete mode 100644 src/main/java/com/retrip/trip/domain/entity/TripParticipants.java rename src/main/java/com/retrip/trip/domain/entity/{TripParticipant.java => participant/Participant.java} (62%) create mode 100644 src/main/java/com/retrip/trip/domain/entity/participant/Participants.java rename src/main/java/com/retrip/trip/domain/exception/{MemberIsNotLeaderException.java => NotLeaderException.java} (71%) create mode 100644 src/main/java/com/retrip/trip/domain/exception/ParticipantFullException.java delete mode 100644 src/main/java/com/retrip/trip/domain/exception/TripFullException.java create mode 100644 src/main/java/com/retrip/trip/domain/service/ParticipantPolicy.java create mode 100644 src/main/java/com/retrip/trip/infra/adapter/out/persistence/mysql/query/ParticipantQuerydslRepository.java create mode 100644 src/test/java/com/retrip/trip/application/in/ParticipantServiceTest.java create mode 100644 src/test/java/com/retrip/trip/application/in/base/BaseParticipantServiceTest.java create mode 100644 src/test/java/com/retrip/trip/domain/entity/ParticipantsTest.java delete mode 100644 src/test/java/com/retrip/trip/domain/entity/TripParticipantsTest.java create mode 100644 src/test/java/com/retrip/trip/domain/fixture/ParticipantFixture.java create mode 100644 src/test/java/com/retrip/trip/domain/service/ParticipantPolicyTest.java diff --git a/src/main/java/com/retrip/trip/application/in/request/TripCreateRequest.java b/src/main/java/com/retrip/trip/application/in/request/TripCreateRequest.java index fb228bb..4099831 100644 --- a/src/main/java/com/retrip/trip/application/in/request/TripCreateRequest.java +++ b/src/main/java/com/retrip/trip/application/in/request/TripCreateRequest.java @@ -14,63 +14,37 @@ @Schema(description = "여행 생성 Request") public record TripCreateRequest( + @Schema(description = "여행 멤버 ID", example = "550e8400-e29b-41d4-a716-446655440000") @NotNull + UUID memberId, + @Schema(description = "여행 위치 ID", example = "550e8400-e29b-41d4-a716-446655440001") @NotNull + UUID locationId, + @Schema(description = "여행 제목", example = "유럽 배낭여행") @NotNull String title, + @Schema(description = "여행 설명", example = "파리, 런던, 로마를 여행하는 일정입니다.") String description, + @Schema(description = "여행 시작 날짜", example = "2025-06-15") @FutureOrPresent LocalDate start, + @Schema(description = "여행 종료 날짜", example = "2025-06-25") @FutureOrPresent LocalDate end, + @Schema(description = "여행 공개 여부") boolean open, + @Schema(description = "여행 최대 참가 인원") int maxParticipants, + @Schema(description = "여행 카테고리") TripCategory category) { - @Schema(description = "여행 멤버 ID", example = "550e8400-e29b-41d4-a716-446655440000") - @NotNull - UUID memberId, - - @Schema(description = "여행 위치 ID", example = "550e8400-e29b-41d4-a716-446655440001") - @NotNull - UUID locationId, - - @Schema(description = "여행 제목", example = "유럽 배낭여행") - @NotNull - String title, - - @Schema(description = "여행 설명", example = "파리, 런던, 로마를 여행하는 일정입니다.") - String description, - - @Schema(description = "여행 시작 날짜", example = "2025-06-15") - @FutureOrPresent - LocalDate start, - - @Schema(description = "여행 종료 날짜", example = "2025-06-25") - @FutureOrPresent - LocalDate end, - - @Schema(description = "여행 공개 여부") - boolean open, - - @Schema(description = "여행 최대 참가 인원") - int maxParticipants, - - @Schema(description = "여행 카테고리") - TripCategory category - -) { public Trip to() { return Trip.create( - memberId, locationId, new TripTitle(title), new TripDescription(description), new TripPeriod(start, end), open, maxParticipants, - category - ); + category); } public Trip toWithItineraries() { return Trip.createWithItineraries( - memberId, locationId, new TripTitle(title), new TripDescription(description), new TripPeriod(start, end), open, maxParticipants, - category - ); + category); } } diff --git a/src/main/java/com/retrip/trip/application/in/response/MyTripResponse.java b/src/main/java/com/retrip/trip/application/in/response/MyTripResponse.java new file mode 100644 index 0000000..8474491 --- /dev/null +++ b/src/main/java/com/retrip/trip/application/in/response/MyTripResponse.java @@ -0,0 +1,16 @@ +package com.retrip.trip.application.in.response; + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.time.LocalDate; +import java.util.UUID; + +@Schema(description = "여행 목록 Response") +public record MyTripResponse( + @Schema(description = "여행 ID") UUID id, + @Schema(description = "여행 제목") String title, + @Schema(description = "목적지 ID") UUID destinationId, + @Schema(description = "여행 시작 날짜") LocalDate start, + @Schema(description = "여행 종료 날짜") LocalDate end, + @Schema(description = "여행 최대 참여자") int maxParticipants, + @Schema(description = "여행 공개 여부") boolean open) {} diff --git a/src/main/java/com/retrip/trip/application/in/response/TripCreateResponse.java b/src/main/java/com/retrip/trip/application/in/response/TripCreateResponse.java index 14931d5..faf9b6e 100644 --- a/src/main/java/com/retrip/trip/application/in/response/TripCreateResponse.java +++ b/src/main/java/com/retrip/trip/application/in/response/TripCreateResponse.java @@ -1,6 +1,7 @@ package com.retrip.trip.application.in.response; import com.retrip.trip.domain.entity.Trip; +import com.retrip.trip.domain.entity.participant.Participant; import io.swagger.v3.oas.annotations.media.Schema; import java.time.LocalDate; @@ -10,37 +11,33 @@ @Schema(description = "여행 생성 Response") public record TripCreateResponse( - @Schema(description = "여행 ID", example = "550e8400-e29b-41d4-a716-446655440000") - UUID id, - + @Schema(description = "여행 ID", example = "550e8400-e29b-41d4-a716-446655440000") UUID id, @Schema(description = "여행 목적지 ID", example = "550e8400-e29b-41d4-a716-446655440001") - UUID destinationId, - - @Schema(description = "여행 제목", example = "파리 여행") - String title, - - @Schema(description = "여행 설명") - String description, - - @Schema(description = "여행 시작 날짜") - LocalDate start, - - @Schema(description = "여행 종료 날짜") - LocalDate end, - - @Schema(description = "여행 공개 여부") - boolean open, - - @Schema(description = "여행 최대 참가 인원") - int maxParticipants, - - @Schema(description = "여행 카테고리") - String category, + UUID destinationId, + @Schema(description = "여행 제목", example = "파리 여행") String title, + @Schema(description = "여행 설명") String description, + @Schema(description = "여행 시작 날짜") LocalDate start, + @Schema(description = "여행 종료 날짜") LocalDate end, + @Schema(description = "여행 공개 여부") boolean open, + @Schema(description = "여행 최대 참가 인원") int maxParticipants, + @Schema(description = "여행 참가 인원") List participants, + @Schema(description = "여행 카테고리") String category, + @Schema(description = "여행 일정 리스트") List itineraries) { + private record ParticipantResponse( + @Schema(description = "참여자 ID") UUID id, + @Schema(description = "참여자 User ID") UUID memberId, + @Schema(description = "참여자 ROLE") String role, + @Schema(description = "참여자 STATUS") String status) { + private static ParticipantResponse of(Participant participant) { + return new ParticipantResponse( + participant.getId(), + participant.getMemberId(), + participant.getRole().getViewName(), + participant.getStatus().name()); + } + } - @Schema(description = "여행 일정 리스트") - List itineraries -) { - public static TripCreateResponse of(Trip trip) { + public static TripCreateResponse of(Trip trip, Participant participant) { return new TripCreateResponse( trip.getId(), trip.getDestinationId(), @@ -49,25 +46,22 @@ public static TripCreateResponse of(Trip trip) { trip.getPeriod().getStart(), trip.getPeriod().getEnd(), trip.isOpen(), - trip.getTripParticipants().getMaxParticipants(), + trip.getMaxParticipants(), + List.of(ParticipantResponse.of(participant)), trip.getCategory().getViewName(), - trip.getItineraries() == null ? new ArrayList<>() : - trip.getItineraries().getValues().stream() - .map(i -> new ItineraryCreateResponse(i.getId(), i.getName(), i.getDate())) - .toList() - ); + trip.getItineraries() == null + ? new ArrayList<>() + : trip.getItineraries().getValues().stream() + .map( + i -> + new ItineraryCreateResponse( + i.getId(), i.getName(), i.getDate())) + .toList()); } @Schema(description = "여행 일정 Response") private record ItineraryCreateResponse( - @Schema(description = "일정 ID") - UUID id, - - @Schema(description = "일정 이름") - String name, - - @Schema(description = "일정 날짜") - LocalDate date - ) { - } + @Schema(description = "일정 ID") UUID id, + @Schema(description = "일정 이름") String name, + @Schema(description = "일정 날짜") LocalDate date) {} } diff --git a/src/main/java/com/retrip/trip/application/in/service/InvitationService.java b/src/main/java/com/retrip/trip/application/in/service/InvitationService.java index 5d9100e..ada6b5d 100644 --- a/src/main/java/com/retrip/trip/application/in/service/InvitationService.java +++ b/src/main/java/com/retrip/trip/application/in/service/InvitationService.java @@ -7,7 +7,7 @@ import com.retrip.trip.application.out.repository.InvitationRepository; import com.retrip.trip.application.out.repository.TripRepository; import com.retrip.trip.domain.entity.Trip; -import com.retrip.trip.domain.entity.TripParticipant; +import com.retrip.trip.domain.entity.participant.Participant; import com.retrip.trip.domain.entity.invitation.Invitation; import com.retrip.trip.domain.entity.invitation.Invitations; import com.retrip.trip.domain.exception.common.EntityNotFoundException; @@ -29,10 +29,12 @@ public class InvitationService implements InvitationManageUseCase { private final TripRepository tripRepository; private final InvitationRepository invitationRepository; + private final ParticipantService participantService; private final InvitationPolicy invitationPolicy; @Override - public InvitationsCreateResponse createInvitations(UUID tripId, TripInvitationsCreateRequest request) { + public InvitationsCreateResponse createInvitations( + UUID tripId, TripInvitationsCreateRequest request) { Trip trip = findTripWithParticipants(tripId); invitationPolicy.canInvite(trip, request.leaderId(), request.memberIds()); Invitations invitations = new Invitations(invitationRepository.findByTripId(tripId)); @@ -44,11 +46,17 @@ public InvitationsCreateResponse createInvitations(UUID tripId, TripInvitationsC @Transactional(readOnly = true) @Override public Page getTripInvitations( - UUID tripId, UUID leaderId, String status, Pageable page, TripInvitationOrder order, String sort) { + UUID tripId, + UUID leaderId, + String status, + Pageable page, + TripInvitationOrder order, + String sort) { invitationPolicy.canViewInvitations(findTripWithParticipants(tripId), leaderId); Pageable pageable = PaginationUtils.createPageRequest(page, order.getField(), sort); Page tripInvitations = - invitationRepository.findByTripIdAndStatus(tripId, InvitationStatus.valueOf(status), pageable); + invitationRepository.findByTripIdAndStatus( + tripId, InvitationStatus.valueOf(status), pageable); return tripInvitations.map(InvitationsResponse::of); } @@ -57,22 +65,25 @@ public Page getMemberInvitations( UUID memberId, String status, Pageable page, TripInvitationOrder order, String sort) { Pageable pageable = PaginationUtils.createPageRequest(page, order.getField(), sort); Page tripInvitations = - invitationRepository.findByMemberIdAndStatus(memberId, InvitationStatus.valueOf(status), pageable); + invitationRepository.findByMemberIdAndStatus( + memberId, InvitationStatus.valueOf(status), pageable); return tripInvitations.map(MemberInvitationResponse::of); } @Override - public MemberInvitationAcceptResponse acceptMemberInvitations(UUID memberId, UUID tripId, UUID invitationId) { + public MemberInvitationAcceptResponse acceptMemberInvitations( + UUID memberId, UUID tripId, UUID invitationId) { Trip trip = findTripWithParticipants(tripId); Invitation invitation = findInvitation(invitationId); invitationPolicy.canAccept(trip, invitation); invitation.accept(); - trip.addParticipant(TripParticipant.createTripParticipant(memberId, trip)); + participantService.createParticipant(memberId, tripId, trip.getMaxParticipants()); return MemberInvitationAcceptResponse.of(invitation); } @Override - public MemberInvitationRejectResponse rejectMemberInvitations(UUID memberId, UUID tripId, UUID invitationId) { + public MemberInvitationRejectResponse rejectMemberInvitations( + UUID memberId, UUID tripId, UUID invitationId) { Invitation invitation = findInvitation(invitationId); invitationPolicy.canReject(invitation); invitation.reject(); @@ -80,12 +91,14 @@ public MemberInvitationRejectResponse rejectMemberInvitations(UUID memberId, UUI } private Trip findTripWithParticipants(UUID tripId) { - return tripRepository.findWithParticipantsById(tripId) + return tripRepository + .findWithParticipantsById(tripId) .orElseThrow(EntityNotFoundException::new); } private Invitation findInvitation(UUID invitationId) { - return invitationRepository.findById(invitationId) + return invitationRepository + .findById(invitationId) .orElseThrow(EntityNotFoundException::new); } } diff --git a/src/main/java/com/retrip/trip/application/in/ItineraryService.java b/src/main/java/com/retrip/trip/application/in/service/ItineraryService.java similarity index 74% rename from src/main/java/com/retrip/trip/application/in/ItineraryService.java rename to src/main/java/com/retrip/trip/application/in/service/ItineraryService.java index 9056718..f677073 100644 --- a/src/main/java/com/retrip/trip/application/in/ItineraryService.java +++ b/src/main/java/com/retrip/trip/application/in/service/ItineraryService.java @@ -1,4 +1,4 @@ -package com.retrip.trip.application.in; +package com.retrip.trip.application.in.service; import com.retrip.trip.application.in.request.ItineraryDetailsCreateRequest; import com.retrip.trip.application.in.request.ItineraryDetailsUpdateRequest; @@ -27,10 +27,12 @@ public class ItineraryService implements ManageItineraryDetailsUseCase, GetItine private final TripItineraryQueryRepository tripItineraryQueryRepository; @Override - public ItineraryDetailsCreateResponse createItineraryDetails(UUID tripId, UUID itineraryId, - ItineraryDetailsCreateRequest request) { - Itinerary itinerary = tripItineraryQueryRepository.findByIdWithItineraryDetails(itineraryId) - .orElseThrow(EntityNotFoundException::new); + public ItineraryDetailsCreateResponse createItineraryDetails( + UUID tripId, UUID itineraryId, ItineraryDetailsCreateRequest request) { + Itinerary itinerary = + tripItineraryQueryRepository + .findByIdWithItineraryDetails(itineraryId) + .orElseThrow(EntityNotFoundException::new); ItineraryDetail itineraryDetail = request.to(itinerary); itinerary.addItineraryDetail(itineraryDetail); return ItineraryDetailsCreateResponse.of(itineraryDetail); @@ -42,8 +44,10 @@ public ItineraryDetailsUpdateResponse updateItineraryDetails( UUID itineraryId, UUID itineraryDetailId, ItineraryDetailsUpdateRequest request) { - Itinerary itinerary = tripItineraryQueryRepository.findByIdWithItineraryDetails(itineraryId) - .orElseThrow(EntityNotFoundException::new); + Itinerary itinerary = + tripItineraryQueryRepository + .findByIdWithItineraryDetails(itineraryId) + .orElseThrow(EntityNotFoundException::new); ItineraryDetail itineraryDetail = request.to(itinerary); itinerary.updateItineraryDetail(itineraryDetail, itineraryDetailId); return ItineraryDetailsUpdateResponse.of(itineraryDetail); @@ -51,8 +55,10 @@ public ItineraryDetailsUpdateResponse updateItineraryDetails( @Override public void deleteItineraryDetail(UUID tripId, UUID itineraryId, UUID itineraryDetailsId) { - Itinerary itinerary = tripItineraryQueryRepository.findByIdWithItineraryDetail(itineraryId, itineraryDetailsId) - .orElseThrow(EntityNotFoundException::new); + Itinerary itinerary = + tripItineraryQueryRepository + .findByIdWithItineraryDetail(itineraryId, itineraryDetailsId) + .orElseThrow(EntityNotFoundException::new); itinerary.removeItineraryDetail(itineraryDetailsId); } diff --git a/src/main/java/com/retrip/trip/application/in/service/ParticipantService.java b/src/main/java/com/retrip/trip/application/in/service/ParticipantService.java new file mode 100644 index 0000000..67119bf --- /dev/null +++ b/src/main/java/com/retrip/trip/application/in/service/ParticipantService.java @@ -0,0 +1,62 @@ +package com.retrip.trip.application.in.service; + +import com.retrip.trip.application.in.usecase.ParticipantManageUseCase; + +import com.retrip.trip.application.out.repository.ParticipantQueryRepository; +import com.retrip.trip.application.out.repository.ParticipantRepository; +import com.retrip.trip.domain.entity.participant.Participant; + +import com.retrip.trip.domain.exception.common.EntityNotFoundException; +import com.retrip.trip.domain.service.ParticipantPolicy; + +import com.retrip.trip.domain.vo.ParticipantRole; + +import java.util.List; +import java.util.UUID; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ParticipantService implements ParticipantManageUseCase { + private final ParticipantPolicy participantPolicy; + private final ParticipantRepository participantRepository; + private final ParticipantQueryRepository participantQueryRepository; + + @Override + public Participant createLeaderParticipant(UUID tripId, UUID memberId) { + return participantRepository.save( + Participant.create(tripId, memberId, ParticipantRole.LEADER)); + } + + @Override + public List findByMemberId(UUID memberId, Pageable page) { + return participantQueryRepository.findByMemberId(memberId, page); + } + + @Override + public Long findByMemberIdTotalCount(UUID memberId) { + return participantQueryRepository.findByMemberId(memberId); + } + + @Override + public Participant createParticipant(UUID tripId, UUID memberId, int maxParticipants) { + Long currentCount = participantQueryRepository.findByTripIdCount(memberId); + participantPolicy.validate(maxParticipants, currentCount); + return participantRepository.save( + Participant.create(tripId, memberId, ParticipantRole.PARTICIPANT)); + } + + @Override + public void updateByLeaderOrThrow(UUID tripId, UUID memberId) { + Participant participant = + participantQueryRepository + .findByTripIdAndMemberId(tripId, memberId) + .orElseThrow(EntityNotFoundException::new); + participantPolicy.validateLeader(participant.getRole()); + } +} diff --git a/src/main/java/com/retrip/trip/application/in/TripService.java b/src/main/java/com/retrip/trip/application/in/service/TripService.java similarity index 63% rename from src/main/java/com/retrip/trip/application/in/TripService.java rename to src/main/java/com/retrip/trip/application/in/service/TripService.java index e0a9351..3cfcf74 100644 --- a/src/main/java/com/retrip/trip/application/in/TripService.java +++ b/src/main/java/com/retrip/trip/application/in/service/TripService.java @@ -1,4 +1,4 @@ -package com.retrip.trip.application.in; +package com.retrip.trip.application.in.service; import com.retrip.trip.application.in.request.DelegateLeaderRequest; import com.retrip.trip.application.in.request.PeriodUpdateRequest; @@ -10,11 +10,17 @@ import com.retrip.trip.domain.entity.Itinerary; import com.retrip.trip.domain.entity.Trip; import com.retrip.trip.domain.entity.TripDemand; +import com.retrip.trip.domain.entity.participant.Participant; import com.retrip.trip.domain.exception.TripNotFoundException; +import com.retrip.trip.domain.service.ParticipantPolicy; import com.retrip.trip.domain.vo.TripPeriod; + import jakarta.persistence.EntityNotFoundException; + import lombok.RequiredArgsConstructor; + import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -26,23 +32,33 @@ @Transactional @Service public class TripService - implements CreateTripUseCase, GetTripUseCase, TripDemandUseCase, TripPeriodUseCase, LeaveTripUseCase, DelegateLeaderUseCase { + implements CreateTripUseCase, + GetTripUseCase, + TripDemandUseCase, + TripPeriodUseCase, + LeaveTripUseCase, + DelegateLeaderUseCase { private final TripRepository tripRepository; private final TripQueryRepository tripQueryRepository; private final TripItineraryQueryRepository tripItineraryQueryRepository; private final TripDemandReadRepository tripDemandReadRepository; - private final TripParticipantRepository tripParticipantRepository; + private final ParticipantService participantService; + private final ParticipantPolicy participantPolicy; @Override public TripCreateResponse createTrip(TripCreateRequest request) { Trip trip = tripRepository.save(request.to()); - return TripCreateResponse.of(trip); + Participant participant = + participantService.createLeaderParticipant(trip.getId(), request.memberId()); + return TripCreateResponse.of(trip, participant); } @Override public TripCreateResponse createTripWithItineraries(TripCreateRequest request) { Trip trip = tripRepository.save(request.toWithItineraries()); - return TripCreateResponse.of(trip); + Participant participant = + participantService.createLeaderParticipant(trip.getId(), request.memberId()); + return TripCreateResponse.of(trip, participant); } @Transactional(readOnly = true) @@ -55,25 +71,30 @@ public Page getTrips(Pageable page) { public TripDemandResponse tripDemand(UUID tripId, TripDemandRequest request) { Trip trip = findTrip(tripId); trip.addDemand(TripDemand.create(request.memberId(), trip, request.message())); - tripRepository.save(trip); return TripDemandResponse.of(trip.getTripDemands().getValues().getLast()); } private Trip findTrip(UUID tripId) { - return tripRepository.findWithParticipantsById(tripId).orElseThrow(TripNotFoundException::new); + return tripRepository + .findWithParticipantsById(tripId) + .orElseThrow(TripNotFoundException::new); } @Override public TripDemandApproveResponse approve(UUID memberId, UUID tripId, UUID tripDemandId) { TripDemand tripDemand = findTripDemandByTripIdAndTripDemandId(tripId, tripDemandId); - tripDemand.approve(memberId); + participantService.updateByLeaderOrThrow(tripId, memberId); + tripDemand.approve(); + participantService.createParticipant( + tripId, memberId, tripDemand.getTrip().getMaxParticipants()); return TripDemandApproveResponse.of(tripDemand); } @Override public TripDemandRejectResponse reject(UUID memberId, UUID tripId, UUID joinRequestId) { TripDemand tripDemand = findTripDemandByTripIdAndTripDemandId(tripId, joinRequestId); - tripDemand.reject(memberId); + participantService.updateByLeaderOrThrow(tripId, memberId); + tripDemand.reject(); return TripDemandRejectResponse.of(tripDemand); } @@ -86,16 +107,27 @@ private TripDemand findTripDemandByTripIdAndTripDemandId(UUID tripId, UUID tripD @Override public PeriodUpdateResponse updatePeriod(UUID tripId, PeriodUpdateRequest request) { TripPeriod period = request.toPeriod(); - Trip trip = tripQueryRepository.findByIdWithItineraries(tripId).orElseThrow(TripNotFoundException::new); - List itineraries = tripItineraryQueryRepository.findByIdsWithItineraryDetails(trip.getItinerariesIds()); + Trip trip = + tripQueryRepository + .findByIdWithItineraries(tripId) + .orElseThrow(TripNotFoundException::new); + List itineraries = + tripItineraryQueryRepository.findByIdsWithItineraryDetails( + trip.getItinerariesIds()); + participantService.updateByLeaderOrThrow(tripId, request.memberId()); trip.updatePeriod(period, request.memberId()); return PeriodUpdateResponse.of(trip); } @Transactional(readOnly = true) @Override - public Page getMyTrips(UUID memberId, Pageable page) { - return tripQueryRepository.findMyTrips(memberId, page); + public Page getMyTrips(UUID memberId, Pageable page) { + List participants = participantService.findByMemberId(memberId, page); + Long total = participantService.findByMemberIdTotalCount(memberId); + List tripIds = participants.stream().map(Participant::getTripId).toList(); + + List trips = tripQueryRepository.findMyTrips(tripIds); + return new PageImpl<>(trips, page, total == null ? 0 : total); } @Override @@ -117,4 +149,4 @@ public DelegateLeaderResponse delegateLeader(UUID tripId, DelegateLeaderRequest trip.delegateLeader(request.currentLeaderId(), request.newLeaderId()); return DelegateLeaderResponse.of(trip, request.newLeaderId()); } -} \ No newline at end of file +} diff --git a/src/main/java/com/retrip/trip/application/in/usecase/GetTripUseCase.java b/src/main/java/com/retrip/trip/application/in/usecase/GetTripUseCase.java index 3085bb0..61be796 100644 --- a/src/main/java/com/retrip/trip/application/in/usecase/GetTripUseCase.java +++ b/src/main/java/com/retrip/trip/application/in/usecase/GetTripUseCase.java @@ -1,5 +1,6 @@ package com.retrip.trip.application.in.usecase; +import com.retrip.trip.application.in.response.MyTripResponse; import com.retrip.trip.application.in.response.TripResponse; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -8,5 +9,6 @@ public interface GetTripUseCase { Page getTrips(Pageable page); - Page getMyTrips(UUID memberId, Pageable page); -} \ No newline at end of file + + Page getMyTrips(UUID memberId, Pageable page); +} diff --git a/src/main/java/com/retrip/trip/application/in/usecase/ParticipantManageUseCase.java b/src/main/java/com/retrip/trip/application/in/usecase/ParticipantManageUseCase.java new file mode 100644 index 0000000..0591583 --- /dev/null +++ b/src/main/java/com/retrip/trip/application/in/usecase/ParticipantManageUseCase.java @@ -0,0 +1,20 @@ +package com.retrip.trip.application.in.usecase; + +import com.retrip.trip.domain.entity.participant.Participant; + +import org.springframework.data.domain.Pageable; + +import java.util.List; +import java.util.UUID; + +public interface ParticipantManageUseCase { + Participant createLeaderParticipant(UUID tripId, UUID memberId); + + List findByMemberId(UUID memberId, Pageable page); + + Long findByMemberIdTotalCount(UUID memberId); + + Participant createParticipant(UUID tripId, UUID memberId, int maxParticipants); + + void updateByLeaderOrThrow(UUID tripId, UUID uuid); +} diff --git a/src/main/java/com/retrip/trip/application/out/repository/ParticipantQueryRepository.java b/src/main/java/com/retrip/trip/application/out/repository/ParticipantQueryRepository.java new file mode 100644 index 0000000..f17e0c6 --- /dev/null +++ b/src/main/java/com/retrip/trip/application/out/repository/ParticipantQueryRepository.java @@ -0,0 +1,22 @@ +package com.retrip.trip.application.out.repository; + +import com.retrip.trip.application.in.response.TripResponse; +import com.retrip.trip.domain.entity.Trip; + +import com.retrip.trip.domain.entity.participant.Participant; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public interface ParticipantQueryRepository { + Long findByMemberId(UUID memberId); + + List findByMemberId(UUID memberId, Pageable page); + + Long findByTripIdCount(UUID tripId); + + Optional findByTripIdAndMemberId(UUID tripId, UUID memberId); +} diff --git a/src/main/java/com/retrip/trip/application/out/repository/ParticipantRepository.java b/src/main/java/com/retrip/trip/application/out/repository/ParticipantRepository.java new file mode 100644 index 0000000..9fcebb2 --- /dev/null +++ b/src/main/java/com/retrip/trip/application/out/repository/ParticipantRepository.java @@ -0,0 +1,11 @@ +package com.retrip.trip.application.out.repository; + +import com.retrip.trip.domain.entity.participant.Participant; + +import java.util.List; +import java.util.UUID; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ParticipantRepository extends JpaRepository {} diff --git a/src/main/java/com/retrip/trip/application/out/repository/TripParticipantRepository.java b/src/main/java/com/retrip/trip/application/out/repository/TripParticipantRepository.java deleted file mode 100644 index 6a8e5b4..0000000 --- a/src/main/java/com/retrip/trip/application/out/repository/TripParticipantRepository.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.retrip.trip.application.out.repository; - -import com.retrip.trip.domain.entity.TripParticipant; -import java.util.List; -import java.util.UUID; -import org.springframework.data.jpa.repository.Query; - -public interface TripParticipantRepository extends ReadRepository { -} diff --git a/src/main/java/com/retrip/trip/application/out/repository/TripQueryRepository.java b/src/main/java/com/retrip/trip/application/out/repository/TripQueryRepository.java index c45fe14..6eb7be2 100644 --- a/src/main/java/com/retrip/trip/application/out/repository/TripQueryRepository.java +++ b/src/main/java/com/retrip/trip/application/out/repository/TripQueryRepository.java @@ -1,19 +1,23 @@ package com.retrip.trip.application.out.repository; +import com.retrip.trip.application.in.response.MyTripResponse; import com.retrip.trip.application.in.response.TripResponse; import com.retrip.trip.domain.entity.Trip; +import java.util.List; import java.util.Optional; import java.util.UUID; +import java.util.stream.Stream; + import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; public interface TripQueryRepository { - Page findTrips(Pageable page); + Page findTrips(Pageable page); - Page findMyTrips(UUID memberId, Pageable page); + List findMyTrips(List tripIds); - Optional findByIdWithItineraries(UUID tripId); -} \ No newline at end of file + Optional findByIdWithItineraries(UUID tripId); +} diff --git a/src/main/java/com/retrip/trip/domain/entity/Trip.java b/src/main/java/com/retrip/trip/domain/entity/Trip.java index 3490d65..e52a7e3 100644 --- a/src/main/java/com/retrip/trip/domain/entity/Trip.java +++ b/src/main/java/com/retrip/trip/domain/entity/Trip.java @@ -1,10 +1,11 @@ package com.retrip.trip.domain.entity; -import com.retrip.trip.domain.exception.LeaderCannotLeaveException; -import com.retrip.trip.domain.exception.NotParticipantException; -import com.retrip.trip.domain.exception.PeriodUpdateFailedException; -import com.retrip.trip.domain.exception.TripNotReadyException; +import com.retrip.trip.domain.entity.participant.Participant; +import com.retrip.trip.domain.entity.participant.Participants; +import com.retrip.trip.domain.exception.*; import com.retrip.trip.domain.exception.common.BusinessException; +import com.retrip.trip.domain.exception.common.ErrorCode; +import com.retrip.trip.domain.exception.common.InvalidValueException; import com.retrip.trip.domain.vo.*; import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; @@ -29,113 +30,102 @@ public class Trip extends BaseEntity { @Id @Column(columnDefinition = "varbinary(16)") private UUID id; + private UUID destinationId; - @Version - private long version; + @Version private long version; - @Embedded - private TripTitle title; + @Embedded private TripTitle title; - @Embedded - private TripDescription description; + @Embedded private TripDescription description; private boolean open; + private int maxParticipants; + @Column(name = "status", length = 50, nullable = false) private TripStatus status; @Column(name = "category", length = 50, nullable = false) private TripCategory category; - @Embedded - private TripParticipants tripParticipants; + @Embedded private Participants participants; - @Embedded - private TripDemands tripDemands; + @Embedded private TripDemands tripDemands; - @Embedded - private TripPeriod period; + @Embedded private TripPeriod period; - @Embedded - private Itineraries itineraries; + @Embedded private Itineraries itineraries; public static Trip create( - UUID memberId, UUID destinationId, TripTitle title, TripDescription description, TripPeriod period, boolean open, int maxParticipants, - TripCategory category - ) { - Trip trip = Trip.builder() - .id(UUID.randomUUID()) - .destinationId(destinationId) - .title(title) - .description(description) - .period(period) - .open(open) - .category(category) - .status(TripStatus.RECRUITING) - .tripDemands(new TripDemands()) - .build(); - trip.tripParticipants = new TripParticipants(memberId, trip, maxParticipants); + TripCategory category) { + validateMaxParticipants(maxParticipants); + Trip trip = + Trip.builder() + .id(UUID.randomUUID()) + .destinationId(destinationId) + .title(title) + .description(description) + .period(period) + .open(open) + .category(category) + .status(TripStatus.RECRUITING) + .maxParticipants(maxParticipants) + .tripDemands(new TripDemands()) + .build(); return trip; } public static Trip createWithItineraries( - UUID leaderId, UUID destinationId, TripTitle title, TripDescription description, TripPeriod period, boolean open, int maxParticipants, - TripCategory category - ) { - Trip trip = Trip.builder() - .id(UUID.randomUUID()) - .destinationId(destinationId) - .title(title) - .description(description) - .period(period) - .open(open) - .category(category) - .status(TripStatus.RECRUITING) - .build(); + TripCategory category) { + validateMaxParticipants(maxParticipants); + Trip trip = + Trip.builder() + .id(UUID.randomUUID()) + .destinationId(destinationId) + .title(title) + .description(description) + .period(period) + .open(open) + .category(category) + .status(TripStatus.RECRUITING) + .maxParticipants(maxParticipants) + .build(); trip.itineraries = new Itineraries(trip, period); - trip.tripParticipants = new TripParticipants(leaderId, trip, maxParticipants); return trip; } - public void addParticipant(TripParticipant participant) { - this.tripParticipants.addParticipant(participant); - } - public void addDemand(TripDemand demand) { validateAddDemand(demand); - validateParticipantLimitNotExceeded(); this.tripDemands.addDemand(demand); } private void validateAddDemand(TripDemand demand) { - if (this.tripParticipants.isBan(demand.getMemberId())) { + if (this.participants.isBan(demand.getMemberId())) { throw new BusinessException(TRIP_MEMBER_BANNED_CANNOT_APPLY); } } - public void validateParticipantLimitNotExceeded() { - tripParticipants.validateCanJoin(); + private static void validateMaxParticipants(int maxParticipants) { + if (maxParticipants < 1) { + throw new InvalidValueException( + ErrorCode.INVALID_MAX_PARTICIPANTS, "최대 참여 인원은 1명 이상이어야 합니다."); + } } - public void updatePeriod( - TripPeriod period, - @NotNull UUID memberId) { - if (!tripParticipants.updatableByLeader(memberId)) { - throw new PeriodUpdateFailedException(); - } + public void updatePeriod(TripPeriod period, @NotNull UUID memberId) { this.period = period; if (Objects.isNull(this.itineraries)) { this.itineraries = new Itineraries(this, period); @@ -152,7 +142,7 @@ public List getItinerariesIds() { } public void banMembers(UUID loginMemberId, List memberIds) { - this.tripParticipants.banMembers(loginMemberId, memberIds, this); + this.participants.banMembers(loginMemberId, memberIds, this); } public void leave(UUID memberId) { @@ -160,20 +150,25 @@ public void leave(UUID memberId) { throw new TripNotReadyException(); } - TripParticipant participant = tripParticipants.findParticipantById(memberId) - .orElseThrow(() -> new NotParticipantException("현재 여행에 참여하고 있지 않습니다.")); + Participant participant = + participants + .findParticipantById(memberId) + .orElseThrow(() -> new NotParticipantException("현재 여행에 참여하고 있지 않습니다.")); if (participant.isLeader()) { throw new LeaderCannotLeaveException(); } - tripParticipants.removeParticipant(memberId); + participants.removeParticipant(memberId); } - public void delegateLeader(UUID currentLeaderId, UUID newLeaderId) { if (this.status != TripStatus.BEFORE_TRIP) { throw new TripNotReadyException(); } - tripParticipants.delegateLeader(currentLeaderId, newLeaderId); + participants.delegateLeader(currentLeaderId, newLeaderId); + } + + public void updateMaxParticipants(int maxParticipants) { + // todo: 최대 참여자 수 변경 함수 이동 } } diff --git a/src/main/java/com/retrip/trip/domain/entity/TripDemand.java b/src/main/java/com/retrip/trip/domain/entity/TripDemand.java index 85e79b3..56e5ae0 100644 --- a/src/main/java/com/retrip/trip/domain/entity/TripDemand.java +++ b/src/main/java/com/retrip/trip/domain/entity/TripDemand.java @@ -25,6 +25,7 @@ public class TripDemand extends BaseEntity { @Id @Column(columnDefinition = "varbinary(16)") private UUID id; + private UUID memberId; @Column(name = "message") @@ -38,33 +39,25 @@ public class TripDemand extends BaseEntity { name = "trip_id", nullable = false, columnDefinition = "varbinary(16)", - foreignKey = @ForeignKey(name = "fk_trip_demand_to_trip") - ) + foreignKey = @ForeignKey(name = "fk_trip_demand_to_trip")) private Trip trip; public static TripDemand create(UUID memberId, Trip trip, String message) { return new TripDemand(UUID.randomUUID(), memberId, message, TripDemandStatus.PENDING, trip); } - public void setStatus(TripDemandStatus status) { - this.status = status; - } - - public void approve(UUID memberId) { - validateTripLeader(memberId); + public void approve() { ensurePending(); this.status = TripDemandStatus.APPROVED; - trip.addParticipant(TripParticipant.createTripParticipant(this.getMemberId(), this.getTrip())); } - public void reject(UUID memberId) { - validateTripLeader(memberId); + public void reject() { ensurePending(); this.status = TripDemandStatus.REJECTED; } private void validateTripLeader(UUID memberId) { - if(!this.trip.getTripParticipants().isLeader(memberId)) { + if (!this.trip.getParticipants().isLeader(memberId)) { throw new BusinessException(NOT_TRIP_LEADER); } } diff --git a/src/main/java/com/retrip/trip/domain/entity/TripParticipants.java b/src/main/java/com/retrip/trip/domain/entity/TripParticipants.java deleted file mode 100644 index b4eadaf..0000000 --- a/src/main/java/com/retrip/trip/domain/entity/TripParticipants.java +++ /dev/null @@ -1,186 +0,0 @@ -package com.retrip.trip.domain.entity; - -import com.retrip.trip.domain.exception.MemberIsNotLeaderException; -import com.retrip.trip.domain.exception.NotParticipantException; -import com.retrip.trip.domain.exception.TripFullException; -import com.retrip.trip.domain.exception.common.BusinessException; -import com.retrip.trip.domain.exception.common.ErrorCode; -import com.retrip.trip.domain.exception.common.BusinessException; -import com.retrip.trip.domain.exception.common.InvalidValueException; -import com.retrip.trip.domain.vo.ParticipantRole; -import com.retrip.trip.domain.vo.ParticipantStatus; -import com.retrip.trip.domain.vo.TripStatus; -import jakarta.persistence.CascadeType; -import jakarta.persistence.Column; -import jakarta.persistence.Embeddable; -import jakarta.persistence.OneToMany; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -import static com.retrip.trip.domain.exception.common.ErrorCode.NOT_TRIP_LEADER; -import static com.retrip.trip.domain.exception.common.ErrorCode.TRIP_MEMBER_NOT_IN_TRIP; -import static lombok.AccessLevel.PROTECTED; - -@Getter -@Embeddable -@NoArgsConstructor(access = PROTECTED, force = true) -public class TripParticipants { - @Column(name = "max_participants", nullable = false) - private int maxParticipants; - - @OneToMany(mappedBy = "trip", cascade = CascadeType.ALL, orphanRemoval = true) - private final List values = new ArrayList<>(); - - public TripParticipants(UUID memberId, Trip trip, int maxParticipants) { - validateMaxParticipants(maxParticipants); - this.maxParticipants = maxParticipants; - TripParticipant leader = TripParticipant.createTripLeader(memberId, trip); - values.add(leader); - } - - public void addParticipant(TripParticipant participant) { - validateCanJoin(); - values.add(participant); - } - - public boolean isFullParticipants() { - return values.size() >= maxParticipants; - } - - public int getCurrentCount() { - return values.size(); - } - - public boolean contains(UUID memberId) { - return values.stream() - .anyMatch(participant -> memberId.equals(participant.getMemberId())); - } - - public void validateCanJoin() { - if (isFullParticipants()) { - throw new TripFullException(); - } - } - - public boolean updatableByLeader(UUID memberId) { - return isLeader(memberId); - } - - public void updateMaxParticipants(int newMaxParticipants, UUID memberId) { - if (!isLeader(memberId)) { - throw new MemberIsNotLeaderException(); - } - validateMaxParticipants(newMaxParticipants); - validateNewMaxParticipants(newMaxParticipants); - this.maxParticipants = newMaxParticipants; - } - - private void validateMaxParticipants(int maxParticipants) { - if (maxParticipants < 1) { - throw new InvalidValueException(ErrorCode.INVALID_MAX_PARTICIPANTS, "최대 참여 인원은 1명 이상이어야 합니다."); - } - } - - private void validateNewMaxParticipants(int newMaxParticipants) { - if (getCurrentCount() > newMaxParticipants) { - throw new InvalidValueException(ErrorCode.INVALID_MAX_PARTICIPANTS, "현재 참여 인원보다 적은 수로 변경할 수 없습니다."); - } - } - - public boolean isLeader(UUID memberId) { - return this.values.stream() - .filter(m -> memberId.equals(m.getMemberId())) - .findFirst() - .orElseThrow(() -> new InvalidValueException(ErrorCode.LEADER_REQUIRED, "여행 회원이 아닙니다.")) - .isLeader(); - } - - public void banMembers(UUID loginMemberId, List memberIds, Trip trip) { - validateTripRecruitingStatus(trip.getStatus()); - validateTripLeader(loginMemberId); - validateExistParticipantMember(memberIds); - - List participantsToBan = values.stream() - .filter(m -> memberIds.contains(m.getMemberId())) - .toList(); - - participantsToBan.forEach(TripParticipant::ban); - } - - private void validateExistParticipantMember(List memberIds) { - boolean isAllExist = values.stream() - .anyMatch(m -> memberIds.contains(m.getMemberId())); - - if(!isAllExist) { - throw new BusinessException(TRIP_MEMBER_NOT_IN_TRIP); - } - } - - private void validateTripLeader(UUID loginMemberId) { - if(!isLeader(loginMemberId)) { - throw new BusinessException(NOT_TRIP_LEADER); - } - } - - public void validateTripRecruitingStatus(TripStatus status) { - if (!TripStatus.RECRUITING.equals(status)) { - throw new IllegalStateException("해당 여행은 모집 중이 아닙니다."); - } - } - - public boolean isBan(UUID memberId) { - return values.stream() - .anyMatch(tripParticipant -> memberId.equals(tripParticipant.getMemberId()) && - tripParticipant.getStatus() == ParticipantStatus.EXPELLED); - } - - public boolean anyDuplicate(List memberIds) { - return values.stream() - .anyMatch(p -> memberIds.contains(p.getMemberId())); - } - - public void removeParticipant(UUID memberId) { - this.values.removeIf(p -> p.getMemberId().equals(memberId)); - } - - public Optional findParticipantById(UUID memberId) { - return this.values.stream() - .filter(p -> p.getMemberId().equals(memberId)) - .findFirst(); - } - - public void delegateLeader(UUID currentLeaderId, UUID newLeaderId) { - validateLeaderDelegation(currentLeaderId, newLeaderId); - - TripParticipant oldLeader = findParticipantById(currentLeaderId) - .orElseThrow(() -> new NotParticipantException("현재 리더를 찾을 수 없습니다.")); - TripParticipant newLeader = findParticipantById(newLeaderId) - .orElseThrow(() -> new NotParticipantException("새로운 리더가 될 멤버가 여행에 참여하고 있지 않습니다.")); - - changeLeader(oldLeader, newLeader); - } - - private void validateLeaderDelegation(UUID currentLeaderId, UUID newLeaderId) { - if (currentLeaderId.equals(newLeaderId)) { - throw new InvalidValueException("자기 자신에게 리더를 위임할 수 없습니다."); - } - if (!isLeader(currentLeaderId)) { - throw new MemberIsNotLeaderException(); - } - } - - private void changeLeader(TripParticipant oldLeader, TripParticipant newLeader) { - oldLeader.changeRole(ParticipantRole.PARTICIPANT); - newLeader.changeRole(ParticipantRole.LEADER); - } - - public boolean isFull() { - return this.values.size() >= this.maxParticipants; - } -} - diff --git a/src/main/java/com/retrip/trip/domain/entity/TripParticipant.java b/src/main/java/com/retrip/trip/domain/entity/participant/Participant.java similarity index 62% rename from src/main/java/com/retrip/trip/domain/entity/TripParticipant.java rename to src/main/java/com/retrip/trip/domain/entity/participant/Participant.java index c72d922..e12f33d 100644 --- a/src/main/java/com/retrip/trip/domain/entity/TripParticipant.java +++ b/src/main/java/com/retrip/trip/domain/entity/participant/Participant.java @@ -1,7 +1,9 @@ -package com.retrip.trip.domain.entity; +package com.retrip.trip.domain.entity.participant; import static lombok.AccessLevel.PROTECTED; +import com.retrip.trip.domain.entity.BaseEntity; +import com.retrip.trip.domain.entity.Trip; import com.retrip.trip.domain.vo.ParticipantRole; import com.retrip.trip.domain.vo.ParticipantStatus; import jakarta.persistence.Column; @@ -22,10 +24,13 @@ @Entity @AllArgsConstructor @NoArgsConstructor(access = PROTECTED, force = true) -public class TripParticipant extends BaseEntity { +public class Participant extends BaseEntity { @Id @Column(columnDefinition = "varbinary(16)") private UUID id; + + private UUID tripId; + private UUID memberId; @Column(name = "role", length = 50, nullable = false) @@ -34,33 +39,22 @@ public class TripParticipant extends BaseEntity { @Column(name = "status", length = 50, nullable = false) private ParticipantStatus status; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn( - name = "trip_id", - nullable = false, - columnDefinition = "varbinary(16)", - foreignKey = @ForeignKey(name = "fk_trip_participant_to_trip") - ) - private Trip trip; - - public static TripParticipant createTripLeader(UUID memberId, Trip trip) { - return new TripParticipant( - UUID.randomUUID(), - memberId, - ParticipantRole.LEADER, - ParticipantStatus.ACTIVE, - trip - ); + public Participant(UUID tripId, UUID memberId, ParticipantRole role) { + this.id = UUID.randomUUID(); + this.memberId = memberId; + this.tripId = tripId; + this.role = role; + this.status = ParticipantStatus.ACTIVE; } - public static TripParticipant createTripParticipant(UUID memberId, Trip trip) { - return new TripParticipant( + public static Participant createTripParticipant(UUID memberId, Trip trip) { + return new Participant( UUID.randomUUID(), + null, memberId, ParticipantRole.PARTICIPANT, ParticipantStatus.ACTIVE, - trip - ); + trip); } public boolean isLeader() { @@ -71,8 +65,11 @@ public void ban() { this.status = ParticipantStatus.EXPELLED; } - public void changeRole(ParticipantRole newRole) { this.role = newRole; } -} \ No newline at end of file + + public static Participant create(UUID tripId, UUID memberId, ParticipantRole role) { + return new Participant(tripId, memberId, role); + } +} diff --git a/src/main/java/com/retrip/trip/domain/entity/participant/Participants.java b/src/main/java/com/retrip/trip/domain/entity/participant/Participants.java new file mode 100644 index 0000000..58eaaee --- /dev/null +++ b/src/main/java/com/retrip/trip/domain/entity/participant/Participants.java @@ -0,0 +1,128 @@ +package com.retrip.trip.domain.entity.participant; + +import com.retrip.trip.domain.entity.Trip; +import com.retrip.trip.domain.exception.NotLeaderException; +import com.retrip.trip.domain.exception.NotParticipantException; +import com.retrip.trip.domain.exception.common.BusinessException; +import com.retrip.trip.domain.exception.common.ErrorCode; +import com.retrip.trip.domain.exception.common.InvalidValueException; +import com.retrip.trip.domain.vo.ParticipantRole; +import com.retrip.trip.domain.vo.ParticipantStatus; +import com.retrip.trip.domain.vo.TripStatus; +import jakarta.persistence.Embeddable; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static com.retrip.trip.domain.exception.common.ErrorCode.NOT_TRIP_LEADER; +import static com.retrip.trip.domain.exception.common.ErrorCode.TRIP_MEMBER_NOT_IN_TRIP; +import static lombok.AccessLevel.PROTECTED; + +@Getter +@Embeddable +@NoArgsConstructor(access = PROTECTED, force = true) +public class Participants { + + public boolean updatableByLeader(UUID memberId) { + return isLeader(memberId); + } + + public boolean isLeader(UUID memberId) { + return this.values.stream() + .filter(m -> memberId.equals(m.getMemberId())) + .findFirst() + .orElseThrow( + () -> new InvalidValueException(ErrorCode.LEADER_REQUIRED, "여행 회원이 아닙니다.")) + .isLeader(); + } + + public void banMembers(UUID loginMemberId, List memberIds, Trip trip) { + validateTripRecruitingStatus(trip.getStatus()); + validateTripLeader(loginMemberId); + validateExistParticipantMember(memberIds); + + List participantsToBan = + values.stream().filter(m -> memberIds.contains(m.getMemberId())).toList(); + + participantsToBan.forEach(Participant::ban); + } + + private void validateExistParticipantMember(List memberIds) { + boolean isAllExist = values.stream().anyMatch(m -> memberIds.contains(m.getMemberId())); + + if (!isAllExist) { + throw new BusinessException(TRIP_MEMBER_NOT_IN_TRIP); + } + } + + private void validateTripLeader(UUID loginMemberId) { + if (!isLeader(loginMemberId)) { + throw new BusinessException(NOT_TRIP_LEADER); + } + } + + public void validateTripRecruitingStatus(TripStatus status) { + if (!TripStatus.RECRUITING.equals(status)) { + throw new IllegalStateException("해당 여행은 모집 중이 아닙니다."); + } + } + + public boolean isBan(UUID memberId) { + return values.stream() + .anyMatch( + tripParticipant -> + memberId.equals(tripParticipant.getMemberId()) + && tripParticipant.getStatus() + == ParticipantStatus.EXPELLED); + } + + public boolean anyDuplicate(List memberIds) { + return values.stream().anyMatch(p -> memberIds.contains(p.getMemberId())); + } + + public void removeParticipant(UUID memberId) { + this.values.removeIf(p -> p.getMemberId().equals(memberId)); + } + + public Optional findParticipantById(UUID memberId) { + return this.values.stream().filter(p -> p.getMemberId().equals(memberId)).findFirst(); + } + + public void delegateLeader(UUID currentLeaderId, UUID newLeaderId) { + validateLeaderDelegation(currentLeaderId, newLeaderId); + + Participant oldLeader = + findParticipantById(currentLeaderId) + .orElseThrow(() -> new NotParticipantException("현재 리더를 찾을 수 없습니다.")); + Participant newLeader = + findParticipantById(newLeaderId) + .orElseThrow( + () -> + new NotParticipantException( + "새로운 리더가 될 멤버가 여행에 참여하고 있지 않습니다.")); + + changeLeader(oldLeader, newLeader); + } + + private void validateLeaderDelegation(UUID currentLeaderId, UUID newLeaderId) { + if (currentLeaderId.equals(newLeaderId)) { + throw new InvalidValueException("자기 자신에게 리더를 위임할 수 없습니다."); + } + if (!isLeader(currentLeaderId)) { + throw new NotLeaderException(); + } + } + + private void changeLeader(Participant oldLeader, Participant newLeader) { + oldLeader.changeRole(ParticipantRole.PARTICIPANT); + newLeader.changeRole(ParticipantRole.LEADER); + } + + public boolean isFull() { + // return this.values.size() >= this.maxParticipants; + return this.values.size() >= 0; + } +} diff --git a/src/main/java/com/retrip/trip/domain/exception/MemberIsNotLeaderException.java b/src/main/java/com/retrip/trip/domain/exception/NotLeaderException.java similarity index 71% rename from src/main/java/com/retrip/trip/domain/exception/MemberIsNotLeaderException.java rename to src/main/java/com/retrip/trip/domain/exception/NotLeaderException.java index c427eb8..44b4115 100644 --- a/src/main/java/com/retrip/trip/domain/exception/MemberIsNotLeaderException.java +++ b/src/main/java/com/retrip/trip/domain/exception/NotLeaderException.java @@ -3,10 +3,10 @@ import com.retrip.trip.domain.exception.common.ErrorCode; import com.retrip.trip.domain.exception.common.InvalidValueException; -public class MemberIsNotLeaderException extends InvalidValueException { +public class NotLeaderException extends InvalidValueException { private static final ErrorCode errorCode = ErrorCode.MEMBER_IS_NOT_LEADER; - public MemberIsNotLeaderException() { + public NotLeaderException() { super(errorCode); } } diff --git a/src/main/java/com/retrip/trip/domain/exception/ParticipantFullException.java b/src/main/java/com/retrip/trip/domain/exception/ParticipantFullException.java new file mode 100644 index 0000000..f4f3da2 --- /dev/null +++ b/src/main/java/com/retrip/trip/domain/exception/ParticipantFullException.java @@ -0,0 +1,16 @@ +package com.retrip.trip.domain.exception; + +import com.retrip.trip.domain.exception.common.BusinessException; +import com.retrip.trip.domain.exception.common.ErrorCode; + +public class ParticipantFullException extends BusinessException { + private static final ErrorCode errorCode = ErrorCode.PARTICIPANT_FULL; + + public ParticipantFullException() { + super(errorCode); + } + + public ParticipantFullException(String message) { + super(errorCode, message); + } +} diff --git a/src/main/java/com/retrip/trip/domain/exception/TripFullException.java b/src/main/java/com/retrip/trip/domain/exception/TripFullException.java deleted file mode 100644 index 3039034..0000000 --- a/src/main/java/com/retrip/trip/domain/exception/TripFullException.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.retrip.trip.domain.exception; - -import com.retrip.trip.domain.exception.common.BusinessException; -import com.retrip.trip.domain.exception.common.ErrorCode; - -public class TripFullException extends BusinessException { - public TripFullException() { - super(ErrorCode.TRIP_FULL); - } -} diff --git a/src/main/java/com/retrip/trip/domain/exception/common/ErrorCode.java b/src/main/java/com/retrip/trip/domain/exception/common/ErrorCode.java index 2e59ba7..4febf3d 100644 --- a/src/main/java/com/retrip/trip/domain/exception/common/ErrorCode.java +++ b/src/main/java/com/retrip/trip/domain/exception/common/ErrorCode.java @@ -19,11 +19,12 @@ public enum ErrorCode { TRIP_INVITATION_DUPLICATE(BAD_REQUEST, "Trip-004", "사용자를 여행에 중복 초대할 수 없습니다."), MEMBER_IS_NOT_LEADER(BAD_REQUEST, "Trip-005", "여행 리더가 아니면 접근할 수 없습니다."), TRIP_MEMBER_NOT_IN_TRIP(BAD_REQUEST, "TRIP-006", "강퇴 대상 멤버가 해당 여행에 포함되어 있지 않습니다."), - TRIP_MEMBER_BANNED_CANNOT_APPLY(HttpStatus.BAD_REQUEST, "TRIP-007", "강퇴된 사용자는 해당 여행에 참가 신청할 수 없습니다."), + TRIP_MEMBER_BANNED_CANNOT_APPLY( + HttpStatus.BAD_REQUEST, "TRIP-007", "강퇴된 사용자는 해당 여행에 참가 신청할 수 없습니다."), LEADER_CANNOT_LEAVE(BAD_REQUEST, "Trip-008", "리더는 여행을 나갈 수 없습니다. 먼저 리더를 위임해야 합니다."), TRIP_NOT_READY(BAD_REQUEST, "Trip-009", "여행이 준비 상태일 때만 나갈 수 있습니다."), NOT_PARTICIPANT(BAD_REQUEST, "Trip-010", "여행 참여자가 아닙니다."), - TRIP_FULL(BAD_REQUEST, "Trip-011", "여행 참가 인원이 가득 찼습니다."), + PARTICIPANT_FULL(BAD_REQUEST, "Trip-011", "여행 최대 인원수보다 참가자가 많을 수 없습니다."), INVALID_MAX_PARTICIPANTS(BAD_REQUEST, "Trip-012", "최대 참여 인원 변경이 불가능합니다."), LEADER_REQUIRED(BAD_REQUEST, "Trip-013", "여행 리더 권한이 필요합니다."), NOT_RECRUITING(BAD_REQUEST, "Trip-014", "모집 중인 여행이 아닙니다."), @@ -32,7 +33,6 @@ public enum ErrorCode { TRIP_PARTICIPANTS_IS_FULL(BAD_REQUEST, "Trip-017", "여행 참여자가 가득 찼습니다."), INVITATION_REJECT_NOT_ALLOWED(BAD_REQUEST, "Trip-018", "초대 거절이 불가능한 상태입니다."); - private final HttpStatus status; private final String code; private final String message; diff --git a/src/main/java/com/retrip/trip/domain/service/InvitationPolicy.java b/src/main/java/com/retrip/trip/domain/service/InvitationPolicy.java index c872b9e..0697d73 100644 --- a/src/main/java/com/retrip/trip/domain/service/InvitationPolicy.java +++ b/src/main/java/com/retrip/trip/domain/service/InvitationPolicy.java @@ -1,7 +1,7 @@ package com.retrip.trip.domain.service; import com.retrip.trip.domain.entity.Trip; -import com.retrip.trip.domain.entity.TripParticipants; +import com.retrip.trip.domain.entity.participant.Participants; import com.retrip.trip.domain.entity.invitation.Invitation; import com.retrip.trip.domain.exception.*; import com.retrip.trip.domain.exception.common.IllegalStateException; @@ -15,13 +15,13 @@ @Service public class InvitationPolicy { public void canInvite(Trip trip, UUID leaderId, List memberIds) { - TripParticipants participants = trip.getTripParticipants(); + Participants participants = trip.getParticipants(); if (trip.getStatus().cannotCreateInvitations()) { throw new IllegalStateException("여행 초대를 생성할 수 없는 상태입니다. " + trip.getStatus().name()); } - if (isNotLeader(trip.getTripParticipants(), leaderId)) { - throw new MemberIsNotLeaderException(); + if (isNotLeader(trip.getParticipants(), leaderId)) { + throw new NotLeaderException(); } if (participants.anyDuplicate(memberIds)) { @@ -30,13 +30,13 @@ public void canInvite(Trip trip, UUID leaderId, List memberIds) { } public void canViewInvitations(Trip trip, UUID leaderId) { - if (isNotLeader(trip.getTripParticipants(), leaderId)) { - throw new MemberIsNotLeaderException(); + if (isNotLeader(trip.getParticipants(), leaderId)) { + throw new NotLeaderException(); } } - public boolean isNotLeader(TripParticipants tripParticipants, UUID leaderId) { - return !tripParticipants.isLeader(leaderId); + public boolean isNotLeader(Participants participants, UUID leaderId) { + return !participants.isLeader(leaderId); } public void canAccept(Trip trip, Invitation invitation) { @@ -46,8 +46,8 @@ public void canAccept(Trip trip, Invitation invitation) { if (trip.getStatus() != RECRUITING) { throw new TripNotRecruitingException(); } - TripParticipants tripParticipants = trip.getTripParticipants(); - if (tripParticipants.isFull()) { + Participants participants = trip.getParticipants(); + if (participants.isFull()) { throw new TripParticipantsIsFullException(); } } diff --git a/src/main/java/com/retrip/trip/domain/service/ParticipantPolicy.java b/src/main/java/com/retrip/trip/domain/service/ParticipantPolicy.java new file mode 100644 index 0000000..4e8d9e9 --- /dev/null +++ b/src/main/java/com/retrip/trip/domain/service/ParticipantPolicy.java @@ -0,0 +1,22 @@ +package com.retrip.trip.domain.service; + +import com.retrip.trip.domain.exception.NotLeaderException; +import com.retrip.trip.domain.exception.ParticipantFullException; +import com.retrip.trip.domain.vo.ParticipantRole; +import org.springframework.stereotype.Service; + +@Service +public class ParticipantPolicy { + + public void validate(int maxParticipants, Long currentCount) { + if (maxParticipants <= currentCount + 1) { + throw new ParticipantFullException(); + } + } + + public void validateLeader(ParticipantRole role) { + if (!role.isLeader()) { + throw new NotLeaderException(); + } + } +} diff --git a/src/main/java/com/retrip/trip/infra/adapter/in/presentation/rest/TripController.java b/src/main/java/com/retrip/trip/infra/adapter/in/presentation/rest/TripController.java index b88ca16..fce9a0a 100644 --- a/src/main/java/com/retrip/trip/infra/adapter/in/presentation/rest/TripController.java +++ b/src/main/java/com/retrip/trip/infra/adapter/in/presentation/rest/TripController.java @@ -79,28 +79,32 @@ public ResponseEntity updatePeriod( @PutMapping("/{tripId}/demand/{tripDemandId}/approve") @Schema(description = "여행 참가 신청 승인") - public ApiResponse approveRequest(@RequestParam("memberId") UUID memberId, //TODO: 추후 로그인 구현되면 이부분은 바뀔 에정 - @PathVariable("tripId") UUID tripId, - @PathVariable("tripDemandId") UUID tripDemandId) { - TripDemandApproveResponse response = tripDemandUseCase.approve(memberId, tripId, tripDemandId); + public ApiResponse approveRequest( + @RequestParam("memberId") UUID memberId, // TODO: 추후 로그인 구현되면 이부분은 바뀔 에정 + @PathVariable("tripId") UUID tripId, + @PathVariable("tripDemandId") UUID tripDemandId) { + TripDemandApproveResponse response = + tripDemandUseCase.approve(memberId, tripId, tripDemandId); return ApiResponse.ok(response); } @PutMapping("/{tripId}/demand/{tripDemandId}/reject") @Schema(description = "여행 참가 신청 거절") - public ApiResponse rejectRequest(@RequestParam("memberId") UUID memberId, //TODO: 추후 로그인 구현되면 이부분은 바뀔 에정 - @PathVariable("tripId") UUID tripId, - @PathVariable("tripDemandId") UUID tripDemandId) { - TripDemandRejectResponse response = tripDemandUseCase.reject(memberId, tripId, tripDemandId); + public ApiResponse rejectRequest( + @RequestParam("memberId") UUID memberId, // TODO: 추후 로그인 구현되면 이부분은 바뀔 에정 + @PathVariable("tripId") UUID tripId, + @PathVariable("tripDemandId") UUID tripDemandId) { + TripDemandRejectResponse response = + tripDemandUseCase.reject(memberId, tripId, tripDemandId); return ApiResponse.ok(response); } @GetMapping("/my") @Schema(description = "나의 여행 목록 조회") - public ApiResponse> getMyTrips( - @RequestParam("memberId") UUID memberId, //TODO: 추후 로그인 구현되면 이부분은 바뀔 에정 + public ApiResponse> getMyTrips( + @RequestParam("memberId") UUID memberId, // TODO: 추후 로그인 구현되면 이부분은 바뀔 에정 @PageableDefault(size = 10, page = 0) Pageable page) { - Page trips = getTripUseCase.getMyTrips(memberId, page); + Page trips = getTripUseCase.getMyTrips(memberId, page); return ApiResponse.ok(trips); } @@ -108,7 +112,7 @@ public ApiResponse> getMyTrips( @Schema(description = "여행 나가기") public ApiResponse leaveTrip( @PathVariable UUID tripId, - @PathVariable UUID memberId) { //TODO: 추후 로그인 구현되면 이부분은 바뀔 에정 + @PathVariable UUID memberId) { // TODO: 추후 로그인 구현되면 이부분은 바뀔 에정 leaveTripUseCase.leaveTrip(tripId, memberId); return ApiResponse.noContent(); } @@ -116,17 +120,17 @@ public ApiResponse leaveTrip( @PutMapping("/{tripId}/delegate-leader") @Schema(description = "여행 리더 위임") public ApiResponse delegateLeader( - @PathVariable UUID tripId, - @RequestBody DelegateLeaderRequest request) { + @PathVariable UUID tripId, @RequestBody DelegateLeaderRequest request) { DelegateLeaderResponse response = delegateLeaderUseCase.delegateLeader(tripId, request); return ApiResponse.ok(response); } @DeleteMapping("/{tripId}/members/ban") @Schema(description = "여행 멤버 리스트 강퇴") - public ApiResponse banMembers(@RequestParam("memberId") UUID memberId, //TODO: 추후 로그인 구현되면 이부분은 바뀔 에정 - @PathVariable("tripId") UUID tripId, - @RequestBody TripMemberBanRequest request) { + public ApiResponse banMembers( + @RequestParam("memberId") UUID memberId, // TODO: 추후 로그인 구현되면 이부분은 바뀔 에정 + @PathVariable("tripId") UUID tripId, + @RequestBody TripMemberBanRequest request) { tripDemandUseCase.banMembers(memberId, tripId, request.memberIds()); return ApiResponse.noContent(); } diff --git a/src/main/java/com/retrip/trip/infra/adapter/out/persistence/mysql/query/ParticipantQuerydslRepository.java b/src/main/java/com/retrip/trip/infra/adapter/out/persistence/mysql/query/ParticipantQuerydslRepository.java new file mode 100644 index 0000000..65c931e --- /dev/null +++ b/src/main/java/com/retrip/trip/infra/adapter/out/persistence/mysql/query/ParticipantQuerydslRepository.java @@ -0,0 +1,68 @@ +package com.retrip.trip.infra.adapter.out.persistence.mysql.query; + +import static com.retrip.trip.domain.entity.participant.QParticipant.participant; +import static com.retrip.trip.domain.vo.ParticipantStatus.ACTIVE; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import com.retrip.trip.application.out.repository.ParticipantQueryRepository; +import com.retrip.trip.domain.entity.participant.Participant; + +import java.util.Optional; + +import lombok.RequiredArgsConstructor; + +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.UUID; + +@RequiredArgsConstructor +@Repository +public class ParticipantQuerydslRepository implements ParticipantQueryRepository { + private final JPAQueryFactory query; + + @Override + public Long findByMemberId(UUID memberId) { + return query.select(participant.count()) + .from(participant) + .where( + participant.memberId.eq(memberId), participant.status.eq(ACTIVE) // 수정된 부분 + ) + .fetchOne(); + } + + @Override + public List findByMemberId(UUID memberId, Pageable page) { + return query.select(participant) + .from(participant) + .where( + participant.memberId.eq(memberId), participant.status.eq(ACTIVE) // 수정된 부분 + ) + .offset(page.getOffset()) + .limit(page.getPageSize()) + .orderBy(participant.createdAt.desc()) + .fetch(); + } + + @Override + public Long findByTripIdCount(UUID tripId) { + return query.select(participant.count()) + .from(participant) + .where(participant.tripId.eq(tripId)) + .fetchOne(); + } + + @Override + public Optional findByTripIdAndMemberId(UUID tripId, UUID memberId) { + return Optional.ofNullable( + query.select(participant) + .from(participant) + .where( + participant.tripId.eq(tripId), + participant.memberId.eq(memberId), + participant.status.eq(ACTIVE) // 수정된 부분 + ) + .fetchOne()); + } +} diff --git a/src/main/java/com/retrip/trip/infra/adapter/out/persistence/mysql/query/TripQuerydslRepository.java b/src/main/java/com/retrip/trip/infra/adapter/out/persistence/mysql/query/TripQuerydslRepository.java index 4c55256..72ace5a 100644 --- a/src/main/java/com/retrip/trip/infra/adapter/out/persistence/mysql/query/TripQuerydslRepository.java +++ b/src/main/java/com/retrip/trip/infra/adapter/out/persistence/mysql/query/TripQuerydslRepository.java @@ -2,6 +2,7 @@ import com.querydsl.core.types.Projections; import com.querydsl.jpa.impl.JPAQueryFactory; +import com.retrip.trip.application.in.response.MyTripResponse; import com.retrip.trip.application.in.response.TripResponse; import com.retrip.trip.application.out.repository.TripQueryRepository; import com.retrip.trip.domain.entity.Trip; @@ -17,82 +18,60 @@ import static com.retrip.trip.domain.entity.QItinerary.itinerary; import static com.retrip.trip.domain.entity.QTrip.trip; -import static com.retrip.trip.domain.entity.QTripParticipant.tripParticipant; import static com.retrip.trip.domain.vo.ParticipantStatus.ACTIVE; @RequiredArgsConstructor @Repository public class TripQuerydslRepository implements TripQueryRepository { - private final JPAQueryFactory query; + private final JPAQueryFactory query; - @Override - public Page findTrips(Pageable page) { - List trips = - query - .select( - Projections.constructor( - TripResponse.class, - trip.id, - trip.title.value, - trip.destinationId, - trip.period.start, - trip.period.end, - trip.open)) - .from(trip) - .offset(page.getOffset()) - .limit(page.getPageSize()) - .orderBy(trip.createdAt.desc()) - .fetch(); - return new PageImpl<>(trips, page, trips.size()); - } + @Override + public Page findTrips(Pageable page) { + List trips = + query.select( + Projections.constructor( + TripResponse.class, + trip.id, + trip.title.value, + trip.destinationId, + trip.period.start, + trip.period.end, + trip.open)) + .from(trip) + .offset(page.getOffset()) + .limit(page.getPageSize()) + .orderBy(trip.createdAt.desc()) + .fetch(); + return new PageImpl<>(trips, page, trips.size()); + } - @Override - public Optional findByIdWithItineraries(UUID tripId) { - return Optional.ofNullable( - query - .selectFrom(trip) - .leftJoin(itinerary) - .on(itinerary.trip.eq(trip)) - .fetchJoin() - .where(trip.id.eq(tripId)) - .orderBy(itinerary.date.desc()) - .fetchOne()); - } + @Override + public Optional findByIdWithItineraries(UUID tripId) { + return Optional.ofNullable( + query.selectFrom(trip) + .leftJoin(itinerary) + .on(itinerary.trip.eq(trip)) + .fetchJoin() + .where(trip.id.eq(tripId)) + .orderBy(itinerary.date.desc()) + .fetchOne()); + } - @Override - public Page findMyTrips(UUID memberId, Pageable page) { - List trips = - query - .select( - Projections.constructor( - TripResponse.class, - trip.id, - trip.title.value, - trip.destinationId, - trip.period.start, - trip.period.end, - trip.open)) - .from(trip) - .join(trip.tripParticipants.values, tripParticipant) - .where( - tripParticipant.memberId.eq(memberId), - tripParticipant.status.eq(ACTIVE) // 수정된 부분 - ) - .offset(page.getOffset()) - .limit(page.getPageSize()) - .orderBy(trip.createdAt.desc()) - .fetch(); - - Long total = query - .select(trip.count()) - .from(trip) - .join(trip.tripParticipants.values, tripParticipant) - .where( - tripParticipant.memberId.eq(memberId), - tripParticipant.status.eq(ACTIVE) // 수정된 부분 - ) - .fetchOne(); - - return new PageImpl<>(trips, page, total == null ? 0 : total); - } -} \ No newline at end of file + @Override + public List findMyTrips(List tripIds) { + return query.select( + Projections.constructor( + MyTripResponse.class, + trip.id, + trip.title.value, + trip.destinationId, + trip.period.start, + trip.period.end, + trip.maxParticipants, + trip.open)) + .from(trip) + .where(trip.id.in(tripIds)) + .orderBy(trip.createdAt.desc()) + .fetch(); + } +} diff --git a/src/test/java/com/retrip/trip/application/in/ParticipantServiceTest.java b/src/test/java/com/retrip/trip/application/in/ParticipantServiceTest.java new file mode 100644 index 0000000..4e0ecb6 --- /dev/null +++ b/src/test/java/com/retrip/trip/application/in/ParticipantServiceTest.java @@ -0,0 +1,65 @@ +package com.retrip.trip.application.in; + +import static com.retrip.trip.domain.fixture.TripFixture.*; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.retrip.trip.application.in.base.BaseInvitationServiceTest; +import com.retrip.trip.application.in.service.ParticipantService; + +import com.retrip.trip.domain.entity.participant.Participant; +import com.retrip.trip.domain.vo.ParticipantRole; +import com.retrip.trip.domain.vo.ParticipantStatus; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.PageRequest; + +import java.util.List; + +class ParticipantServiceTest extends BaseInvitationServiceTest { + @Autowired private ParticipantService participantService; + + @Test + void 리더_참여자를_생성한다() { + // given + + // when + Participant response = participantService.createLeaderParticipant(TRIP_ID, MEMBER_ID); + + // then + assertThat(response.getTripId()).isEqualTo(TRIP_ID); + assertThat(response.getRole()).isEqualTo(ParticipantRole.LEADER); + assertThat(response.getStatus()).isEqualTo(ParticipantStatus.ACTIVE); + } + + @Test + void 내가_참여중인_여행을_볼_수_있다() { + // given + participantService.createLeaderParticipant(TRIP_ID, MEMBER_ID); + participantService.createLeaderParticipant(TRIP_ID, MEMBER_ID); + participantService.createLeaderParticipant(TRIP_ID, MEMBER_ID); + participantService.createLeaderParticipant(TRIP_ID, MEMBER_ID); + + // when + List response = + participantService.findByMemberId(MEMBER_ID, PageRequest.of(0, 10)); + + // then + assertThat(response.size()).isEqualTo(4); + } + + @Test + void 내가_참여중인_여행을_총_갯수를_볼_수_있다() { + // given + participantService.createLeaderParticipant(TRIP_ID, MEMBER_ID); + participantService.createLeaderParticipant(TRIP_ID, MEMBER_ID); + participantService.createLeaderParticipant(TRIP_ID, MEMBER_ID); + participantService.createLeaderParticipant(TRIP_ID, MEMBER_ID); + + // when + Long response = participantService.findByMemberIdTotalCount(MEMBER_ID); + + // then + assertThat(response).isEqualTo(4); + } +} diff --git a/src/test/java/com/retrip/trip/application/in/TripServiceTest.java b/src/test/java/com/retrip/trip/application/in/TripServiceTest.java index e386342..014f287 100644 --- a/src/test/java/com/retrip/trip/application/in/TripServiceTest.java +++ b/src/test/java/com/retrip/trip/application/in/TripServiceTest.java @@ -3,9 +3,9 @@ import com.retrip.trip.application.in.base.BaseTripServiceTest; import com.retrip.trip.application.in.request.*; import com.retrip.trip.application.in.response.*; +import com.retrip.trip.domain.entity.participant.Participant; import com.retrip.trip.domain.entity.Trip; import com.retrip.trip.domain.entity.TripDemand; -import com.retrip.trip.domain.entity.TripParticipant; import com.retrip.trip.domain.exception.*; import com.retrip.trip.domain.exception.common.BusinessException; import com.retrip.trip.domain.exception.common.InvalidValueException; @@ -34,7 +34,6 @@ private Trip createTestTrip(String title, String description, TripCategory categ TripPeriod period = createFuturePeriod(); Trip trip = Trip.create( - memberId, UUID.randomUUID(), new TripTitle(title), new TripDescription(description), @@ -46,29 +45,29 @@ private Trip createTestTrip(String title, String description, TripCategory categ } private Trip createReadyTrip(UUID leaderId) { - Trip trip = Trip.create( - leaderId, - locationId, - new TripTitle("준비된 여행"), - new TripDescription("설명"), - new TripPeriod(LocalDate.now().plusDays(1), LocalDate.now().plusDays(5)), - true, - 4, - TripCategory.DOMESTIC); + Trip trip = + Trip.create( + locationId, + new TripTitle("준비된 여행"), + new TripDescription("설명"), + new TripPeriod(LocalDate.now().plusDays(1), LocalDate.now().plusDays(5)), + true, + 4, + TripCategory.DOMESTIC); ReflectionTestUtils.setField(trip, "status", TripStatus.BEFORE_TRIP); return tripRepository.save(trip); } private Trip createProgressTrip(UUID leaderId) { - Trip trip = Trip.create( - leaderId, - locationId, - new TripTitle("진행중 여행"), - new TripDescription("설명"), - new TripPeriod(LocalDate.now().plusDays(1), LocalDate.now().plusDays(5)), - true, - 4, - TripCategory.DOMESTIC); + Trip trip = + Trip.create( + locationId, + new TripTitle("진행중 여행"), + new TripDescription("설명"), + new TripPeriod(LocalDate.now().plusDays(1), LocalDate.now().plusDays(5)), + true, + 4, + TripCategory.DOMESTIC); ReflectionTestUtils.setField(trip, "status", TripStatus.IN_PROGRESS); return tripRepository.save(trip); } @@ -96,7 +95,6 @@ private Trip createProgressTrip(UUID leaderId) { TripPeriod period = createFuturePeriod(); tripRepository.save( Trip.createWithItineraries( - memberId, UUID.randomUUID(), new TripTitle("속초 여행 멤버 구함"), new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), @@ -106,7 +104,6 @@ private Trip createProgressTrip(UUID leaderId) { TripCategory.DOMESTIC)); tripRepository.save( Trip.createWithItineraries( - memberId, UUID.randomUUID(), new TripTitle("강릉 여행 멤버 구함"), new TripDescription("강릉 여행은 이렇게이렇게 갈겁니다~"), @@ -116,7 +113,6 @@ private Trip createProgressTrip(UUID leaderId) { TripCategory.DOMESTIC)); tripRepository.save( Trip.createWithItineraries( - memberId, UUID.randomUUID(), new TripTitle("대구 여행 멤버 구함"), new TripDescription("대구 여행은 이렇게이렇게 갈겁니다~"), @@ -126,7 +122,6 @@ private Trip createProgressTrip(UUID leaderId) { TripCategory.DOMESTIC)); tripRepository.save( Trip.createWithItineraries( - memberId, UUID.randomUUID(), new TripTitle("부산 여행 멤버 구함"), new TripDescription("부산 여행은 이렇게이렇게 갈겁니다~"), @@ -160,19 +155,22 @@ private Trip createProgressTrip(UUID leaderId) { @Test void 리더가_참여_요청을_승인하면_실제_참여자로_등록된다() { // given - Trip newTrip = TripFixture.createTestTrip(memberId, "승인 테스트 여행", "여행 설명", TripCategory.DOMESTIC); + Trip newTrip = + TripFixture.createTestTrip(memberId, "승인 테스트 여행", "여행 설명", TripCategory.DOMESTIC); TripDemand tripDemand = TripDemand.create(newMemberId, newTrip, "참여 요청 메시지"); newTrip.getTripDemands().getValues().add(tripDemand); tripRepository.save(newTrip); // then - TripDemandApproveResponse response = tripService.approve(memberId, newTrip.getId(), tripDemand.getId()); + TripDemandApproveResponse response = + tripService.approve(memberId, newTrip.getId(), tripDemand.getId()); Trip trip = tripRepository.findById(newTrip.getId()).orElseThrow(); - UUID newParticipantMemberId = trip.getTripParticipants().getValues().stream() - .map(TripParticipant::getMemberId) - .filter(id -> id.equals(newMemberId)) - .findFirst() - .orElseThrow(); + UUID newParticipantMemberId = + trip.getParticipants().getValues().stream() + .map(Participant::getMemberId) + .filter(id -> id.equals(newMemberId)) + .findFirst() + .orElseThrow(); // when assertThat(response).isNotNull(); @@ -183,26 +181,31 @@ private Trip createProgressTrip(UUID leaderId) { @Test void 리더가_아니면_참여_요청을_승인할_수_없다() { // given - Trip newTrip = TripFixture.createTestTrip(memberId, "승인 테스트 여행", "여행 설명", TripCategory.DOMESTIC); + Trip newTrip = + TripFixture.createTestTrip(memberId, "승인 테스트 여행", "여행 설명", TripCategory.DOMESTIC); TripDemand tripDemand = TripDemand.create(newMemberId, newTrip, "참여 요청 메시지"); newTrip.getTripDemands().getValues().add(tripDemand); tripRepository.save(newTrip); tripService.approve(memberId, newTrip.getId(), tripDemand.getId()); // then && when - assertThrows(BusinessException.class, () -> tripService.approve(newMemberId, newTrip.getId(), tripDemand.getId())); + assertThrows( + BusinessException.class, + () -> tripService.approve(newMemberId, newTrip.getId(), tripDemand.getId())); } @Test void 리더가_참여_요청을_거절하면_요청_상태가_거절로_변경된다() { // given - Trip newTrip = TripFixture.createTestTrip(memberId, "거절 테스트 여행", "여행 설명", TripCategory.DOMESTIC); + Trip newTrip = + TripFixture.createTestTrip(memberId, "거절 테스트 여행", "여행 설명", TripCategory.DOMESTIC); TripDemand tripDemand = TripDemand.create(newMemberId, newTrip, "참여 요청 메시지"); newTrip.getTripDemands().getValues().add(tripDemand); tripRepository.save(newTrip); // then - TripDemandRejectResponse response = tripService.reject(memberId, newTrip.getId(), tripDemand.getId()); + TripDemandRejectResponse response = + tripService.reject(memberId, newTrip.getId(), tripDemand.getId()); // when assertThat(response).isNotNull(); @@ -212,14 +215,17 @@ private Trip createProgressTrip(UUID leaderId) { @Test void 리더가_아니면_참여_요청을_거절할_수_없다() { // given - Trip newTrip = TripFixture.createTestTrip(memberId, "거절 테스트 여행", "여행 설명", TripCategory.DOMESTIC); + Trip newTrip = + TripFixture.createTestTrip(memberId, "거절 테스트 여행", "여행 설명", TripCategory.DOMESTIC); TripDemand tripDemand = TripDemand.create(newMemberId, newTrip, "참여 요청 메시지"); newTrip.getTripDemands().getValues().add(tripDemand); tripRepository.save(newTrip); tripService.approve(memberId, newTrip.getId(), tripDemand.getId()); // then && when - assertThrows(BusinessException.class, () -> tripService.reject(newMemberId, newTrip.getId(), tripDemand.getId())); + assertThrows( + BusinessException.class, + () -> tripService.reject(newMemberId, newTrip.getId(), tripDemand.getId())); } @Test @@ -230,11 +236,7 @@ private Trip createProgressTrip(UUID leaderId) { // then LocalDate start = LocalDate.now().plusDays(1); LocalDate end = LocalDate.now().plusDays(3); - PeriodUpdateRequest request = TripRequestFixture.createPeriod( - memberId, - start, - end - ); + PeriodUpdateRequest request = TripRequestFixture.createPeriod(memberId, start, end); PeriodUpdateResponse response = tripService.updatePeriod(trip.getId(), request); // when @@ -247,211 +249,210 @@ private Trip createProgressTrip(UUID leaderId) { @DisplayName("변경 일자가 '이전 일정 시작 전 ~ 이전 일정 시작 전'으로 변경할 수 있다") void updatePeriodIsBeforePrePeriodStart() { // given - TripPeriod period = new TripPeriod(LocalDate.now().plusDays(5), LocalDate.now().plusDays(10)); - Trip trip = tripRepository.save( - Trip.createWithItineraries( - memberId, - UUID.randomUUID(), - new TripTitle("강릉 여행 멤버 구함"), - new TripDescription("강릉 여행은 이렇게이렇게 갈겁니다~"), - period, - true, - 4, - TripCategory.DOMESTIC) - ); - + TripPeriod period = + new TripPeriod(LocalDate.now().plusDays(5), LocalDate.now().plusDays(10)); + Trip trip = + tripRepository.save( + Trip.createWithItineraries( + UUID.randomUUID(), + new TripTitle("강릉 여행 멤버 구함"), + new TripDescription("강릉 여행은 이렇게이렇게 갈겁니다~"), + period, + true, + 4, + TripCategory.DOMESTIC)); // when - PeriodUpdateRequest request = TripRequestFixture.createPeriod( - memberId, - LocalDate.now().plusDays(1), - LocalDate.now().plusDays(3) - ); + PeriodUpdateRequest request = + TripRequestFixture.createPeriod( + memberId, LocalDate.now().plusDays(1), LocalDate.now().plusDays(3)); PeriodUpdateResponse response = tripService.updatePeriod(trip.getId(), request); // then assertThat(response.itineraries()).hasSize(3); - assertThat(response.itineraries().stream() - .map(PeriodUpdateResponse.ItineraryUpdateResponse::name).toList()) + assertThat( + response.itineraries().stream() + .map(PeriodUpdateResponse.ItineraryUpdateResponse::name) + .toList()) .containsExactly("day 1", "day 2", "day 3"); - assertThat(response.itineraries().stream() - .map(PeriodUpdateResponse.ItineraryUpdateResponse::date).toList()) + assertThat( + response.itineraries().stream() + .map(PeriodUpdateResponse.ItineraryUpdateResponse::date) + .toList()) .containsExactly( LocalDate.now().plusDays(1), LocalDate.now().plusDays(2), - LocalDate.now().plusDays(3) - ); + LocalDate.now().plusDays(3)); } - @Test @DisplayName("변경 일자가 '이전 일정 시작 전 ~ 이전 일정 종료 전'으로 변경할 수 있다") void updatePeriodBetweenPrePeriodStartBeforeAndPrePeriodEndBefore() { // given - TripPeriod period = new TripPeriod(LocalDate.now().plusDays(5), LocalDate.now().plusDays(10)); - Trip trip = tripRepository.save( - Trip.createWithItineraries( - memberId, - UUID.randomUUID(), - new TripTitle("강릉 여행 멤버 구함"), - new TripDescription("강릉 여행은 이렇게이렇게 갈겁니다~"), - period, - true, - 4, - TripCategory.DOMESTIC) - ); - + TripPeriod period = + new TripPeriod(LocalDate.now().plusDays(5), LocalDate.now().plusDays(10)); + Trip trip = + tripRepository.save( + Trip.createWithItineraries( + UUID.randomUUID(), + new TripTitle("강릉 여행 멤버 구함"), + new TripDescription("강릉 여행은 이렇게이렇게 갈겁니다~"), + period, + true, + 4, + TripCategory.DOMESTIC)); // when - PeriodUpdateRequest request = TripRequestFixture.createPeriod( - memberId, - LocalDate.now().plusDays(3), - LocalDate.now().plusDays(8) - ); + PeriodUpdateRequest request = + TripRequestFixture.createPeriod( + memberId, LocalDate.now().plusDays(3), LocalDate.now().plusDays(8)); PeriodUpdateResponse response = tripService.updatePeriod(trip.getId(), request); // then assertThat(response.itineraries()).hasSize(6); - assertThat(response.itineraries().stream() - .map(PeriodUpdateResponse.ItineraryUpdateResponse::name).toList()) + assertThat( + response.itineraries().stream() + .map(PeriodUpdateResponse.ItineraryUpdateResponse::name) + .toList()) .containsExactly("day 1", "day 2", "day 3", "day 4", "day 5", "day 6"); - assertThat(response.itineraries().stream() - .map(PeriodUpdateResponse.ItineraryUpdateResponse::date).toList()) + assertThat( + response.itineraries().stream() + .map(PeriodUpdateResponse.ItineraryUpdateResponse::date) + .toList()) .containsExactly( LocalDate.now().plusDays(3), LocalDate.now().plusDays(4), LocalDate.now().plusDays(5), LocalDate.now().plusDays(6), LocalDate.now().plusDays(7), - LocalDate.now().plusDays(8) - ); + LocalDate.now().plusDays(8)); } @Test @DisplayName("변경 일자가 '이전 일정 시작 전 ~ 이전 일정 종료 후'으로 변경할 수 있다") void updatePeriodBetweenPrePeriodStartBeforeAndPrePeriodEndAfter() { // given - TripPeriod period = new TripPeriod(LocalDate.now().plusDays(5), LocalDate.now().plusDays(7)); - Trip trip = tripRepository.save( - Trip.createWithItineraries( - memberId, - UUID.randomUUID(), - new TripTitle("강릉 여행 멤버 구함"), - new TripDescription("강릉 여행은 이렇게이렇게 갈겁니다~"), - period, - true, - 4, - TripCategory.DOMESTIC) - ); - + TripPeriod period = + new TripPeriod(LocalDate.now().plusDays(5), LocalDate.now().plusDays(7)); + Trip trip = + tripRepository.save( + Trip.createWithItineraries( + UUID.randomUUID(), + new TripTitle("강릉 여행 멤버 구함"), + new TripDescription("강릉 여행은 이렇게이렇게 갈겁니다~"), + period, + true, + 4, + TripCategory.DOMESTIC)); // when - PeriodUpdateRequest request = TripRequestFixture.createPeriod( - memberId, - LocalDate.now().plusDays(3), - LocalDate.now().plusDays(8) - ); + PeriodUpdateRequest request = + TripRequestFixture.createPeriod( + memberId, LocalDate.now().plusDays(3), LocalDate.now().plusDays(8)); PeriodUpdateResponse response = tripService.updatePeriod(trip.getId(), request); // then assertThat(response.itineraries()).hasSize(6); - assertThat(response.itineraries().stream() - .map(PeriodUpdateResponse.ItineraryUpdateResponse::name).toList()) + assertThat( + response.itineraries().stream() + .map(PeriodUpdateResponse.ItineraryUpdateResponse::name) + .toList()) .containsExactly("day 1", "day 2", "day 3", "day 4", "day 5", "day 6"); - assertThat(response.itineraries().stream() - .map(PeriodUpdateResponse.ItineraryUpdateResponse::date).toList()) + assertThat( + response.itineraries().stream() + .map(PeriodUpdateResponse.ItineraryUpdateResponse::date) + .toList()) .containsExactly( LocalDate.now().plusDays(3), LocalDate.now().plusDays(4), LocalDate.now().plusDays(5), LocalDate.now().plusDays(6), LocalDate.now().plusDays(7), - LocalDate.now().plusDays(8) - ); + LocalDate.now().plusDays(8)); } @Test @DisplayName("변경 일자가 '이전 일정 시작 후 ~ 이전 일정 종료 전'으로 변경할 수 있다") void updatePeriodBetweenPrePeriodStartAfterAndPrePeriodEndBefore() { // given - TripPeriod period = new TripPeriod(LocalDate.now().plusDays(5), LocalDate.now().plusDays(10)); - Trip trip = tripRepository.save( - Trip.createWithItineraries( - memberId, - UUID.randomUUID(), - new TripTitle("강릉 여행 멤버 구함"), - new TripDescription("강릉 여행은 이렇게이렇게 갈겁니다~"), - period, - true, - 4, - TripCategory.DOMESTIC) - ); - + TripPeriod period = + new TripPeriod(LocalDate.now().plusDays(5), LocalDate.now().plusDays(10)); + Trip trip = + tripRepository.save( + Trip.createWithItineraries( + UUID.randomUUID(), + new TripTitle("강릉 여행 멤버 구함"), + new TripDescription("강릉 여행은 이렇게이렇게 갈겁니다~"), + period, + true, + 4, + TripCategory.DOMESTIC)); // when - PeriodUpdateRequest request = TripRequestFixture.createPeriod( - memberId, - LocalDate.now().plusDays(7), - LocalDate.now().plusDays(9) - ); + PeriodUpdateRequest request = + TripRequestFixture.createPeriod( + memberId, LocalDate.now().plusDays(7), LocalDate.now().plusDays(9)); PeriodUpdateResponse response = tripService.updatePeriod(trip.getId(), request); // then assertThat(response.itineraries()).hasSize(3); - assertThat(response.itineraries().stream() - .map(PeriodUpdateResponse.ItineraryUpdateResponse::name).toList()) + assertThat( + response.itineraries().stream() + .map(PeriodUpdateResponse.ItineraryUpdateResponse::name) + .toList()) .containsExactly("day 1", "day 2", "day 3"); - assertThat(response.itineraries().stream() - .map(PeriodUpdateResponse.ItineraryUpdateResponse::date).toList()) + assertThat( + response.itineraries().stream() + .map(PeriodUpdateResponse.ItineraryUpdateResponse::date) + .toList()) .containsExactly( LocalDate.now().plusDays(7), LocalDate.now().plusDays(8), - LocalDate.now().plusDays(9) - ); + LocalDate.now().plusDays(9)); } @Test @DisplayName("변경 일자가 '이전 일정 시작 후 ~ 이전 일정 종료 후'으로 변경할 수 있다") void updatePeriodBetweenPrePeriodStartAfterAndPrePeriodEndAfter() { // given - TripPeriod period = new TripPeriod(LocalDate.now().plusDays(5), LocalDate.now().plusDays(10)); - Trip trip = tripRepository.save( - Trip.createWithItineraries( - memberId, - UUID.randomUUID(), - new TripTitle("강릉 여행 멤버 구함"), - new TripDescription("강릉 여행은 이렇게이렇게 갈겁니다~"), - period, - true, - 4, - TripCategory.DOMESTIC) - ); - + TripPeriod period = + new TripPeriod(LocalDate.now().plusDays(5), LocalDate.now().plusDays(10)); + Trip trip = + tripRepository.save( + Trip.createWithItineraries( + UUID.randomUUID(), + new TripTitle("강릉 여행 멤버 구함"), + new TripDescription("강릉 여행은 이렇게이렇게 갈겁니다~"), + period, + true, + 4, + TripCategory.DOMESTIC)); // when - PeriodUpdateRequest request = TripRequestFixture.createPeriod( - memberId, - LocalDate.now().plusDays(7), - LocalDate.now().plusDays(12) - ); + PeriodUpdateRequest request = + TripRequestFixture.createPeriod( + memberId, LocalDate.now().plusDays(7), LocalDate.now().plusDays(12)); PeriodUpdateResponse response = tripService.updatePeriod(trip.getId(), request); // then assertThat(response.itineraries()).hasSize(6); - assertThat(response.itineraries().stream() - .map(PeriodUpdateResponse.ItineraryUpdateResponse::name).toList()) + assertThat( + response.itineraries().stream() + .map(PeriodUpdateResponse.ItineraryUpdateResponse::name) + .toList()) .containsExactly("day 1", "day 2", "day 3", "day 4", "day 5", "day 6"); - assertThat(response.itineraries().stream() - .map(PeriodUpdateResponse.ItineraryUpdateResponse::date).toList()) + assertThat( + response.itineraries().stream() + .map(PeriodUpdateResponse.ItineraryUpdateResponse::date) + .toList()) .containsExactly( LocalDate.now().plusDays(7), LocalDate.now().plusDays(8), LocalDate.now().plusDays(9), LocalDate.now().plusDays(10), LocalDate.now().plusDays(11), - LocalDate.now().plusDays(12) - ); + LocalDate.now().plusDays(12)); } @Test @@ -459,41 +460,41 @@ void updatePeriodBetweenPrePeriodStartAfterAndPrePeriodEndAfter() { void updatePeriodIsAfterPrePeriodEndAfter() { // given - TripPeriod period = new TripPeriod(LocalDate.now().plusDays(5), LocalDate.now().plusDays(10)); - Trip trip = tripRepository.save( - Trip.createWithItineraries( - memberId, - UUID.randomUUID(), - new TripTitle("강릉 여행 멤버 구함"), - new TripDescription("강릉 여행은 이렇게이렇게 갈겁니다~"), - period, - true, - 4, - TripCategory.DOMESTIC) - ); - + TripPeriod period = + new TripPeriod(LocalDate.now().plusDays(5), LocalDate.now().plusDays(10)); + Trip trip = + tripRepository.save( + Trip.createWithItineraries( + UUID.randomUUID(), + new TripTitle("강릉 여행 멤버 구함"), + new TripDescription("강릉 여행은 이렇게이렇게 갈겁니다~"), + period, + true, + 4, + TripCategory.DOMESTIC)); // when - PeriodUpdateRequest request = TripRequestFixture.createPeriod( - memberId, - LocalDate.now().plusDays(11), - LocalDate.now().plusDays(14) - ); + PeriodUpdateRequest request = + TripRequestFixture.createPeriod( + memberId, LocalDate.now().plusDays(11), LocalDate.now().plusDays(14)); PeriodUpdateResponse response = tripService.updatePeriod(trip.getId(), request); // then assertThat(response.itineraries()).hasSize(4); - assertThat(response.itineraries().stream() - .map(PeriodUpdateResponse.ItineraryUpdateResponse::name).toList()) + assertThat( + response.itineraries().stream() + .map(PeriodUpdateResponse.ItineraryUpdateResponse::name) + .toList()) .containsExactly("day 1", "day 2", "day 3", "day 4"); - assertThat(response.itineraries().stream() - .map(PeriodUpdateResponse.ItineraryUpdateResponse::date).toList()) + assertThat( + response.itineraries().stream() + .map(PeriodUpdateResponse.ItineraryUpdateResponse::date) + .toList()) .containsExactly( LocalDate.now().plusDays(11), LocalDate.now().plusDays(12), LocalDate.now().plusDays(13), - LocalDate.now().plusDays(14) - ); + LocalDate.now().plusDays(14)); } @Test @@ -501,7 +502,7 @@ void updatePeriodIsAfterPrePeriodEndAfter() { void leaveTrip_success_forMember() { // given Trip trip = createReadyTrip(memberId); - trip.addParticipant(TripParticipant.createTripParticipant(newMemberId, trip)); + trip.addParticipant(Participant.createTripParticipant(newMemberId, trip)); tripRepository.save(trip); // when @@ -509,7 +510,8 @@ void leaveTrip_success_forMember() { // then Trip updatedTrip = tripRepository.findById(trip.getId()).get(); - boolean isParticipantPresent = updatedTrip.getTripParticipants().findParticipantById(newMemberId).isPresent(); + boolean isParticipantPresent = + updatedTrip.getParticipants().findParticipantById(newMemberId).isPresent(); assertThat(isParticipantPresent).isFalse(); } @@ -520,9 +522,11 @@ void leaveTrip_fail_forLeader() { Trip trip = createReadyTrip(memberId); // when & then - assertThrows(LeaderCannotLeaveException.class, () -> { - tripService.leaveTrip(trip.getId(), memberId); - }); + assertThrows( + LeaderCannotLeaveException.class, + () -> { + tripService.leaveTrip(trip.getId(), memberId); + }); } @Test @@ -530,13 +534,15 @@ void leaveTrip_fail_forLeader() { void leaveTrip_fail_whenTripNotReady() { // given Trip trip = createProgressTrip(memberId); - trip.addParticipant(TripParticipant.createTripParticipant(newMemberId, trip)); + trip.addParticipant(Participant.createTripParticipant(newMemberId, trip)); tripRepository.save(trip); // when & then - assertThrows(TripNotReadyException.class, () -> { - tripService.leaveTrip(trip.getId(), newMemberId); - }); + assertThrows( + TripNotReadyException.class, + () -> { + tripService.leaveTrip(trip.getId(), newMemberId); + }); } @Test @@ -544,7 +550,7 @@ void leaveTrip_fail_whenTripNotReady() { void delegateLeader_success() { // given Trip trip = createReadyTrip(memberId); - trip.addParticipant(TripParticipant.createTripParticipant(newMemberId, trip)); + trip.addParticipant(Participant.createTripParticipant(newMemberId, trip)); tripRepository.save(trip); DelegateLeaderRequest request = new DelegateLeaderRequest(memberId, newMemberId); @@ -553,8 +559,9 @@ void delegateLeader_success() { // then Trip updatedTrip = tripRepository.findById(trip.getId()).get(); - assertTrue(updatedTrip.getTripParticipants().findParticipantById(newMemberId).get().isLeader()); - assertThat(updatedTrip.getTripParticipants().findParticipantById(memberId).get().isLeader()).isFalse(); + assertTrue(updatedTrip.getParticipants().findParticipantById(newMemberId).get().isLeader()); + assertThat(updatedTrip.getParticipants().findParticipantById(memberId).get().isLeader()) + .isFalse(); } @Test @@ -562,14 +569,16 @@ void delegateLeader_success() { void delegateLeader_fail_notLeader() { // given Trip trip = createReadyTrip(memberId); - trip.addParticipant(TripParticipant.createTripParticipant(newMemberId, trip)); + trip.addParticipant(Participant.createTripParticipant(newMemberId, trip)); tripRepository.save(trip); DelegateLeaderRequest request = new DelegateLeaderRequest(newMemberId, memberId); // when & then - assertThrows(MemberIsNotLeaderException.class, () -> { - tripService.delegateLeader(trip.getId(), request); - }); + assertThrows( + NotLeaderException.class, + () -> { + tripService.delegateLeader(trip.getId(), request); + }); } @Test @@ -580,9 +589,11 @@ void delegateLeader_fail_toSelf() { DelegateLeaderRequest request = new DelegateLeaderRequest(memberId, memberId); // when & then - assertThrows(InvalidValueException.class, () -> { - tripService.delegateLeader(trip.getId(), request); - }); + assertThrows( + InvalidValueException.class, + () -> { + tripService.delegateLeader(trip.getId(), request); + }); } @Test @@ -594,9 +605,11 @@ void delegateLeader_fail_toNonParticipant() { DelegateLeaderRequest request = new DelegateLeaderRequest(memberId, nonParticipantId); // when & then - assertThrows(NotParticipantException.class, () -> { - tripService.delegateLeader(trip.getId(), request); - }); + assertThrows( + NotParticipantException.class, + () -> { + tripService.delegateLeader(trip.getId(), request); + }); } @Test @@ -604,12 +617,12 @@ void delegateLeader_fail_toNonParticipant() { void getMyTrips_afterLeaving() { // given Trip trip = createReadyTrip(memberId); - trip.addParticipant(TripParticipant.createTripParticipant(newMemberId, trip)); + trip.addParticipant(Participant.createTripParticipant(newMemberId, trip)); tripRepository.save(trip); // when tripService.leaveTrip(trip.getId(), newMemberId); - Page myTrips = tripService.getMyTrips(newMemberId, PageRequest.of(0, 10)); + Page myTrips = tripService.getMyTrips(newMemberId, PageRequest.of(0, 10)); // then assertThat(myTrips.getTotalElements()).isZero(); @@ -618,20 +631,23 @@ void getMyTrips_afterLeaving() { @Test void 리더는_참여자들을_추방할_수_있다() { // given - Trip newTrip = TripFixture.createTestTrip(memberId, "승인 테스트 여행", "여행 설명", TripCategory.DOMESTIC); + Trip newTrip = + TripFixture.createTestTrip(memberId, "승인 테스트 여행", "여행 설명", TripCategory.DOMESTIC); TripDemand tripDemand = TripDemand.create(newMemberId, newTrip, "참여 요청 메시지"); newTrip.getTripDemands().getValues().add(tripDemand); tripRepository.save(newTrip); - TripDemandApproveResponse response = tripService.approve(memberId, newTrip.getId(), tripDemand.getId()); + TripDemandApproveResponse response = + tripService.approve(memberId, newTrip.getId(), tripDemand.getId()); // then tripService.banMembers(memberId, newTrip.getId(), List.of(newMemberId)); Trip trip = tripRepository.findById(newTrip.getId()).orElseThrow(); - TripParticipant banParticipant = trip.getTripParticipants().getValues().stream() - .filter(participant -> participant.getMemberId().equals(newMemberId)) - .findFirst() - .orElseThrow(); + Participant banParticipant = + trip.getParticipants().getValues().stream() + .filter(participant -> participant.getMemberId().equals(newMemberId)) + .findFirst() + .orElseThrow(); // when assertThat(response).isNotNull(); @@ -641,7 +657,8 @@ void getMyTrips_afterLeaving() { @Test void 해당_여행에_강퇴당한_사용자는_다시_참여요청할_수_없다() { // given - Trip newTrip = TripFixture.createTestTrip(memberId, "승인 테스트 여행", "여행 설명", TripCategory.DOMESTIC); + Trip newTrip = + TripFixture.createTestTrip(memberId, "승인 테스트 여행", "여행 설명", TripCategory.DOMESTIC); TripDemand tripDemand = TripDemand.create(newMemberId, newTrip, "참여 요청 메시지"); newTrip.getTripDemands().getValues().add(tripDemand); tripRepository.save(newTrip); @@ -651,57 +668,58 @@ void getMyTrips_afterLeaving() { TripDemandRequest request = new TripDemandRequest(newMemberId, "강퇴당한 후 다시 참여 요청 메시지"); // when && then - assertThrows(BusinessException.class, () -> tripService.tripDemand(newTrip.getId(), request)); + assertThrows( + BusinessException.class, () -> tripService.tripDemand(newTrip.getId(), request)); } @Test @DisplayName("여행이 가득 찼을 경우, 새로운 사용자는 참여 요청을 할 수 없다.") void tripIsFull_then_cannotJoin() { // given - Trip trip = Trip.create( - memberId, - UUID.randomUUID(), - new TripTitle("꽉 찬 여행"), - new TripDescription("더 이상 자리가 없어요"), - new TripPeriod(LocalDate.now().plusDays(1), LocalDate.now().plusDays(5)), - true, - 1, - TripCategory.DOMESTIC - ); + Trip trip = + Trip.create( + UUID.randomUUID(), + new TripTitle("꽉 찬 여행"), + new TripDescription("더 이상 자리가 없어요"), + new TripPeriod(LocalDate.now().plusDays(1), LocalDate.now().plusDays(5)), + true, + 1, + TripCategory.DOMESTIC); tripRepository.save(trip); // when & then TripDemandRequest newRequest = new TripDemandRequest(UUID.randomUUID(), "저도 참여하고 싶어요!"); - assertThrows(TripFullException.class, () -> { - tripService.tripDemand(trip.getId(), newRequest); - }); + assertThrows( + ParticipantFullException.class, + () -> { + tripService.tripDemand(trip.getId(), newRequest); + }); } @Test @DisplayName("여행 리더는 최대 참여 인원을 변경할 수 있다.") void leader_can_update_maxParticipants() { // given - Trip trip = Trip.create( - memberId, - UUID.randomUUID(), - new TripTitle("인원 변경 테스트"), - new TripDescription("설명"), - new TripPeriod(LocalDate.now().plusDays(1), LocalDate.now().plusDays(5)), - true, - 3, - TripCategory.DOMESTIC - ); + Trip trip = + Trip.create( + UUID.randomUUID(), + new TripTitle("인원 변경 테스트"), + new TripDescription("설명"), + new TripPeriod(LocalDate.now().plusDays(1), LocalDate.now().plusDays(5)), + true, + 3, + TripCategory.DOMESTIC); tripRepository.save(trip); // when int newMaxParticipants = 5; - trip.getTripParticipants().updateMaxParticipants(newMaxParticipants,memberId); + trip.getParticipants().updateMaxParticipants(newMaxParticipants, memberId); tripRepository.save(trip); // then Trip updatedTrip = tripRepository.findById(trip.getId()).get(); - assertThat(updatedTrip.getTripParticipants().getMaxParticipants()).isEqualTo(newMaxParticipants); + assertThat(updatedTrip.getMaxParticipants()).isEqualTo(newMaxParticipants); } @Test @@ -709,48 +727,49 @@ void leader_can_update_maxParticipants() { void nonLeader_cannot_update_maxParticipants() { // given UUID nonLeaderId = UUID.randomUUID(); - Trip trip = Trip.create( - memberId, - UUID.randomUUID(), - new TripTitle("권한 테스트"), - new TripDescription("설명"), - new TripPeriod(LocalDate.now().plusDays(1), LocalDate.now().plusDays(5)), - true, - 3, - TripCategory.DOMESTIC - ); - trip.addParticipant(TripParticipant.createTripParticipant(nonLeaderId, trip)); + Trip trip = + Trip.create( + UUID.randomUUID(), + new TripTitle("권한 테스트"), + new TripDescription("설명"), + new TripPeriod(LocalDate.now().plusDays(1), LocalDate.now().plusDays(5)), + true, + 3, + TripCategory.DOMESTIC); + trip.addParticipant(Participant.createTripParticipant(nonLeaderId, trip)); tripRepository.save(trip); - // when & then - assertThrows(MemberIsNotLeaderException.class, () -> { - trip.getTripParticipants().updateMaxParticipants(5, nonLeaderId); - }); + assertThrows( + NotLeaderException.class, + () -> { + trip.getParticipants().updateMaxParticipants(5, nonLeaderId); + }); } @Test @DisplayName("최대 참여 인원을 현재 참여 인원보다 적게 변경할 수 없다.") void cannot_update_maxParticipants_lessThan_currentParticipants() { // given - Trip trip = Trip.create( - memberId, - UUID.randomUUID(), - new TripTitle("인원 축소 테스트"), - new TripDescription("설명"), - new TripPeriod(LocalDate.now().plusDays(1), LocalDate.now().plusDays(5)), - true, - 4, - TripCategory.DOMESTIC - ); - trip.addParticipant(TripParticipant.createTripParticipant(UUID.randomUUID(), trip)); - trip.addParticipant(TripParticipant.createTripParticipant(UUID.randomUUID(), trip)); + Trip trip = + Trip.create( + UUID.randomUUID(), + new TripTitle("인원 축소 테스트"), + new TripDescription("설명"), + new TripPeriod(LocalDate.now().plusDays(1), LocalDate.now().plusDays(5)), + true, + 4, + TripCategory.DOMESTIC); + trip.addParticipant(Participant.createTripParticipant(UUID.randomUUID(), trip)); + trip.addParticipant(Participant.createTripParticipant(UUID.randomUUID(), trip)); tripRepository.save(trip); // when & then - assertThrows(InvalidValueException.class, () -> { - trip.getTripParticipants().updateMaxParticipants(2,memberId); - }); + assertThrows( + InvalidValueException.class, + () -> { + trip.getParticipants().updateMaxParticipants(2, memberId); + }); } @Test @@ -758,29 +777,29 @@ void cannot_update_maxParticipants_lessThan_currentParticipants() { void isLeader_check_works_correctly() { // given UUID nonLeaderId = UUID.randomUUID(); - Trip trip = Trip.create( - memberId, - UUID.randomUUID(), - new TripTitle("isLeader 테스트"), - new TripDescription("설명"), - new TripPeriod(LocalDate.now().plusDays(1), LocalDate.now().plusDays(5)), - true, - 3, - TripCategory.DOMESTIC - ); - trip.addParticipant(TripParticipant.createTripParticipant(nonLeaderId, trip)); + Trip trip = + Trip.create( + UUID.randomUUID(), + new TripTitle("isLeader 테스트"), + new TripDescription("설명"), + new TripPeriod(LocalDate.now().plusDays(1), LocalDate.now().plusDays(5)), + true, + 3, + TripCategory.DOMESTIC); + trip.addParticipant(Participant.createTripParticipant(nonLeaderId, trip)); tripRepository.save(trip); // when Trip savedTrip = tripRepository.findById(trip.getId()).get(); - boolean isLeaderResult = savedTrip.getTripParticipants().isLeader(memberId); - boolean isNotLeaderResult = savedTrip.getTripParticipants().isLeader(nonLeaderId); - + boolean isLeaderResult = savedTrip.getParticipants().isLeader(memberId); + boolean isNotLeaderResult = savedTrip.getParticipants().isLeader(nonLeaderId); // then assertTrue(isLeaderResult); - assertThrows(InvalidValueException.class, () -> { - savedTrip.getTripParticipants().isLeader(UUID.randomUUID()); - }); + assertThrows( + InvalidValueException.class, + () -> { + savedTrip.getParticipants().isLeader(UUID.randomUUID()); + }); } } diff --git a/src/test/java/com/retrip/trip/application/in/base/BaseItineraryServiceTest.java b/src/test/java/com/retrip/trip/application/in/base/BaseItineraryServiceTest.java index 8466c32..497d3d2 100644 --- a/src/test/java/com/retrip/trip/application/in/base/BaseItineraryServiceTest.java +++ b/src/test/java/com/retrip/trip/application/in/base/BaseItineraryServiceTest.java @@ -1,7 +1,7 @@ package com.retrip.trip.application.in.base; import com.querydsl.jpa.impl.JPAQueryFactory; -import com.retrip.trip.application.in.ItineraryService; +import com.retrip.trip.application.in.service.ItineraryService; import com.retrip.trip.application.out.repository.TripItineraryQueryRepository; import com.retrip.trip.application.out.repository.TripRepository; import com.retrip.trip.domain.entity.Trip; @@ -17,20 +17,16 @@ import java.util.UUID; public abstract class BaseItineraryServiceTest extends BaseServiceTest { - @Autowired - protected TripRepository tripRepository; - @Autowired - protected JPAQueryFactory jpaQueryFactory; + @Autowired protected TripRepository tripRepository; + @Autowired protected JPAQueryFactory jpaQueryFactory; protected TripItineraryQueryRepository tripItineraryQueryRepository; - protected UUID memberId = UUID.fromString("c076d246-7e6d-4191-bf5c-310aebf4c003"); protected UUID locationId = UUID.fromString("13c8ab91-76bc-4f70-93e9-89f1a65dc64a"); protected ItineraryService itineraryService; protected Trip trip = Trip.createWithItineraries( - memberId, UUID.randomUUID(), new TripTitle("속초 여행 멤버 구함"), new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), @@ -43,6 +39,5 @@ public abstract class BaseItineraryServiceTest extends BaseServiceTest { void setUp() { tripItineraryQueryRepository = new TripItineraryQuerydslRepository(jpaQueryFactory); itineraryService = new ItineraryService(tripItineraryQueryRepository); - } } diff --git a/src/test/java/com/retrip/trip/application/in/base/BaseParticipantServiceTest.java b/src/test/java/com/retrip/trip/application/in/base/BaseParticipantServiceTest.java new file mode 100644 index 0000000..98b8913 --- /dev/null +++ b/src/test/java/com/retrip/trip/application/in/base/BaseParticipantServiceTest.java @@ -0,0 +1,40 @@ +package com.retrip.trip.application.in.base; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import com.retrip.trip.application.in.service.ParticipantService; +import com.retrip.trip.application.in.service.TripService; +import com.retrip.trip.application.out.repository.*; +import com.retrip.trip.domain.service.InvitationPolicy; +import com.retrip.trip.domain.service.ParticipantPolicy; +import com.retrip.trip.infra.adapter.out.persistence.mysql.query.ParticipantQuerydslRepository; +import com.retrip.trip.infra.adapter.out.persistence.mysql.query.TripItineraryQuerydslRepository; +import com.retrip.trip.infra.adapter.out.persistence.mysql.query.TripQuerydslRepository; + +import jakarta.persistence.EntityManager; + +import org.junit.jupiter.api.BeforeEach; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.UUID; + +public abstract class BaseParticipantServiceTest extends BaseServiceTest { + @Autowired protected EntityManager em; + @Autowired protected JPAQueryFactory jpaQueryFactory; + @Autowired protected ParticipantRepository participantRepository; + + protected ParticipantService participantService; + protected ParticipantPolicy participantPolicy = new ParticipantPolicy(); + protected ParticipantQueryRepository participantQueryRepository; + + protected UUID memberId = UUID.fromString("c076d246-7e6d-4191-bf5c-310aebf4c003"); + protected UUID locationId = UUID.fromString("13c8ab91-76bc-4f70-93e9-89f1a65dc64a"); + protected UUID newMemberId = UUID.fromString("11111111-2222-3333-4444-555555555555"); + + @BeforeEach + void setUp() { + participantQueryRepository = new ParticipantQuerydslRepository(jpaQueryFactory); + participantService = + new ParticipantService( + participantPolicy, participantRepository, participantQueryRepository); + } +} diff --git a/src/test/java/com/retrip/trip/application/in/base/BaseTripServiceTest.java b/src/test/java/com/retrip/trip/application/in/base/BaseTripServiceTest.java index 49369e2..72903f0 100644 --- a/src/test/java/com/retrip/trip/application/in/base/BaseTripServiceTest.java +++ b/src/test/java/com/retrip/trip/application/in/base/BaseTripServiceTest.java @@ -1,8 +1,11 @@ package com.retrip.trip.application.in.base; import com.querydsl.jpa.impl.JPAQueryFactory; -import com.retrip.trip.application.in.TripService; +import com.retrip.trip.application.in.service.ParticipantService; +import com.retrip.trip.application.in.service.TripService; import com.retrip.trip.application.out.repository.*; +import com.retrip.trip.domain.service.ParticipantPolicy; +import com.retrip.trip.infra.adapter.out.persistence.mysql.query.ParticipantQuerydslRepository; import com.retrip.trip.infra.adapter.out.persistence.mysql.query.TripItineraryQuerydslRepository; import com.retrip.trip.infra.adapter.out.persistence.mysql.query.TripQuerydslRepository; import jakarta.persistence.EntityManager; @@ -12,22 +15,19 @@ import java.util.UUID; public abstract class BaseTripServiceTest extends BaseServiceTest { - @Autowired - protected TripRepository tripRepository; + @Autowired protected TripRepository tripRepository; - @Autowired - protected EntityManager em; + @Autowired protected EntityManager em; - @Autowired - protected TripDemandReadRepository tripDemandReadRepository; + @Autowired protected TripDemandReadRepository tripDemandReadRepository; + @Autowired protected ParticipantRepository participantRepository; + protected ParticipantQueryRepository participantQueryRepository; - @Autowired - protected TripParticipantRepository tripParticipantRepository; - - @Autowired - protected JPAQueryFactory jpaQueryFactory; + @Autowired protected JPAQueryFactory jpaQueryFactory; protected TripService tripService; + protected ParticipantPolicy participantPolicy; + protected ParticipantService participantService; protected TripQueryRepository tripQueryRepository; protected TripItineraryQueryRepository tripItineraryQueryRepository; @@ -39,9 +39,17 @@ public abstract class BaseTripServiceTest extends BaseServiceTest { void setUp() { tripQueryRepository = new TripQuerydslRepository(jpaQueryFactory); tripItineraryQueryRepository = new TripItineraryQuerydslRepository(jpaQueryFactory); - + participantPolicy = new ParticipantPolicy(); + participantQueryRepository = new ParticipantQuerydslRepository(jpaQueryFactory); + participantService = + new ParticipantService( + participantPolicy, participantRepository, participantQueryRepository); tripService = new TripService( - tripRepository, tripQueryRepository, tripItineraryQueryRepository, tripDemandReadRepository, tripParticipantRepository); + tripRepository, + tripQueryRepository, + tripItineraryQueryRepository, + tripDemandReadRepository, + participantService); } } diff --git a/src/test/java/com/retrip/trip/application/in/request/TripRequestFixture.java b/src/test/java/com/retrip/trip/application/in/request/TripRequestFixture.java index f76e681..6ef7dd3 100644 --- a/src/test/java/com/retrip/trip/application/in/request/TripRequestFixture.java +++ b/src/test/java/com/retrip/trip/application/in/request/TripRequestFixture.java @@ -13,10 +13,10 @@ public static PeriodUpdateRequest createPeriod(UUID memberId, LocalDate start, L return new PeriodUpdateRequest(memberId, start, end); } - public static Trip createTestTrip(UUID memberId, String title, String description, TripCategory category) { + public static Trip createTestTrip( + UUID memberId, String title, String description, TripCategory category) { TripPeriod period = createFuturePeriod(); return Trip.create( - memberId, UUID.randomUUID(), new TripTitle(title), new TripDescription(description), diff --git a/src/test/java/com/retrip/trip/domain/entity/ItinerariesTest.java b/src/test/java/com/retrip/trip/domain/entity/ItinerariesTest.java index 64f68d1..27a539e 100644 --- a/src/test/java/com/retrip/trip/domain/entity/ItinerariesTest.java +++ b/src/test/java/com/retrip/trip/domain/entity/ItinerariesTest.java @@ -21,19 +21,17 @@ class ItinerariesTest { @DisplayName("여행 기간의 일자 만큼 일정 목록을 생성 한다.") @Test void ofPeriod() { - TripPeriod period = new TripPeriod( - LocalDate.now().plusDays(1), - LocalDate.now().plusDays(6)); - Trip trip = Trip.createWithItineraries( - memberId, - UUID.randomUUID(), - new TripTitle("속초 여행 멤버 구함"), - new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), - period, - true, - 4, - TripCategory.DOMESTIC - ); + TripPeriod period = + new TripPeriod(LocalDate.now().plusDays(1), LocalDate.now().plusDays(6)); + Trip trip = + Trip.createWithItineraries( + UUID.randomUUID(), + new TripTitle("속초 여행 멤버 구함"), + new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), + period, + true, + 4, + TripCategory.DOMESTIC); Itineraries itineraries = new Itineraries(trip, period); assertThat(itineraries.getValues().size()).isEqualTo(6); } @@ -41,50 +39,43 @@ void ofPeriod() { @DisplayName("일정의 날짜가 기간을 벗어나면 예외가 발생한다.") @Test void out_of_period() { - TripPeriod period = new TripPeriod( - LocalDate.now().plusDays(0), - LocalDate.now().plusDays(5) - ); - List dates = List.of( - LocalDate.now().minusDays(1), - LocalDate.now().plusDays(0)); - - Trip trip = Trip.createWithItineraries( - memberId, - UUID.randomUUID(), - new TripTitle("속초 여행 멤버 구함"), - new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), - period, - true, - 4, - TripCategory.DOMESTIC - ); + TripPeriod period = + new TripPeriod(LocalDate.now().plusDays(0), LocalDate.now().plusDays(5)); + List dates = List.of(LocalDate.now().minusDays(1), LocalDate.now().plusDays(0)); + + Trip trip = + Trip.createWithItineraries( + UUID.randomUUID(), + new TripTitle("속초 여행 멤버 구함"), + new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), + period, + true, + 4, + TripCategory.DOMESTIC); assertThatThrownBy(() -> new Itineraries(trip, period, dates)) .isExactlyInstanceOf(IllegalArgumentException.class); - } @DisplayName("날짜 목록으로 일정을 생성한다.") @Test void irregular() { - TripPeriod period = new TripPeriod( - LocalDate.now().plusDays(1), - LocalDate.now().plusDays(6)); - List dates = List.of( - LocalDate.now().plusDays(1), - LocalDate.now().plusDays(3), - LocalDate.now().plusDays(6)); - - Trip trip = Trip.createWithItineraries( - memberId, - UUID.randomUUID(), - new TripTitle("속초 여행 멤버 구함"), - new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), - period, - true, - 4, - TripCategory.DOMESTIC - ); + TripPeriod period = + new TripPeriod(LocalDate.now().plusDays(1), LocalDate.now().plusDays(6)); + List dates = + List.of( + LocalDate.now().plusDays(1), + LocalDate.now().plusDays(3), + LocalDate.now().plusDays(6)); + + Trip trip = + Trip.createWithItineraries( + UUID.randomUUID(), + new TripTitle("속초 여행 멤버 구함"), + new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), + period, + true, + 4, + TripCategory.DOMESTIC); Itineraries itineraries = new Itineraries(trip, period, dates); assertThat(itineraries.getValues().size()).isEqualTo(3); assertThat(itineraries.getValues().get(0).getName()).isEqualTo("day 1"); @@ -93,26 +84,26 @@ void irregular() { @Test @DisplayName("변경 일자가 '이전 일정 시작 전 ~ 이전 일정 시작 전'으로 변경할 수 있다") void updatePeriodIsBeforePrePeriodStart() { - //given - TripPeriod period = new TripPeriod(LocalDate.now().plusDays(5), LocalDate.now().plusDays(15)); - Trip trip = Trip.createWithItineraries( - memberId, - UUID.randomUUID(), - new TripTitle("속초 여행 멤버 구함"), - new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), - period, - true, - 4, - TripCategory.DOMESTIC - ); + // given + TripPeriod period = + new TripPeriod(LocalDate.now().plusDays(5), LocalDate.now().plusDays(15)); + Trip trip = + Trip.createWithItineraries( + UUID.randomUUID(), + new TripTitle("속초 여행 멤버 구함"), + new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), + period, + true, + 4, + TripCategory.DOMESTIC); Itineraries itineraries = new Itineraries(trip, period); - //when - TripPeriod updatePeriod = new TripPeriod(LocalDate.now().plusDays(1), LocalDate.now().plusDays(3)); + // when + TripPeriod updatePeriod = + new TripPeriod(LocalDate.now().plusDays(1), LocalDate.now().plusDays(3)); itineraries.updateByPeriod(updatePeriod, trip); - - //then + // then assertThat(itineraries.getValues()).hasSize(3); assertThat(itineraries.getValues().stream().map(Itinerary::getName).toList()) .containsExactly("day 1", "day 2", "day 3"); @@ -120,33 +111,32 @@ void updatePeriodIsBeforePrePeriodStart() { .containsExactly( LocalDate.now().plusDays(1), LocalDate.now().plusDays(2), - LocalDate.now().plusDays(3) - ); + LocalDate.now().plusDays(3)); } @Test @DisplayName("변경 일자가 '이전 일정 시작 전 ~ 이전 일정 종료 전'으로 변경할 수 있다") void updatePeriodBetweenPrePeriodStartBeforeAndPrePeriodEndBefore() { - //given - TripPeriod period = new TripPeriod(LocalDate.now().plusDays(5), LocalDate.now().plusDays(15)); - Trip trip = Trip.createWithItineraries( - memberId, - UUID.randomUUID(), - new TripTitle("속초 여행 멤버 구함"), - new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), - period, - true, - 4, - TripCategory.DOMESTIC - ); + // given + TripPeriod period = + new TripPeriod(LocalDate.now().plusDays(5), LocalDate.now().plusDays(15)); + Trip trip = + Trip.createWithItineraries( + UUID.randomUUID(), + new TripTitle("속초 여행 멤버 구함"), + new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), + period, + true, + 4, + TripCategory.DOMESTIC); Itineraries itineraries = new Itineraries(trip, period); - //when - TripPeriod updatePeriod = new TripPeriod(LocalDate.now().plusDays(3), LocalDate.now().plusDays(8)); + // when + TripPeriod updatePeriod = + new TripPeriod(LocalDate.now().plusDays(3), LocalDate.now().plusDays(8)); itineraries.updateByPeriod(updatePeriod, trip); - - //then + // then assertThat(itineraries.getValues()).hasSize(6); assertThat(itineraries.getValues().stream().map(Itinerary::getName).toList()) .containsExactly("day 1", "day 2", "day 3", "day 4", "day 5", "day 6"); @@ -157,32 +147,32 @@ void updatePeriodBetweenPrePeriodStartBeforeAndPrePeriodEndBefore() { LocalDate.now().plusDays(5), LocalDate.now().plusDays(6), LocalDate.now().plusDays(7), - LocalDate.now().plusDays(8) - ); + LocalDate.now().plusDays(8)); } @Test @DisplayName("변경 일자가 '이전 일정 시작 전 ~ 이전 일정 종료 후'으로 변경할 수 있다") void updatePeriodBetweenPrePeriodStartBeforeAndPrePeriodEndAfter() { - //given - TripPeriod period = new TripPeriod(LocalDate.now().plusDays(5), LocalDate.now().plusDays(7)); - Trip trip = Trip.createWithItineraries( - memberId, - UUID.randomUUID(), - new TripTitle("속초 여행 멤버 구함"), - new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), - period, - true, - 4, - TripCategory.DOMESTIC - ); + // given + TripPeriod period = + new TripPeriod(LocalDate.now().plusDays(5), LocalDate.now().plusDays(7)); + Trip trip = + Trip.createWithItineraries( + UUID.randomUUID(), + new TripTitle("속초 여행 멤버 구함"), + new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), + period, + true, + 4, + TripCategory.DOMESTIC); Itineraries itineraries = new Itineraries(trip, period); - //when - TripPeriod updatePeriod = new TripPeriod(LocalDate.now().plusDays(3), LocalDate.now().plusDays(8)); + // when + TripPeriod updatePeriod = + new TripPeriod(LocalDate.now().plusDays(3), LocalDate.now().plusDays(8)); itineraries.updateByPeriod(updatePeriod, trip); - //then + // then assertThat(itineraries.getValues()).hasSize(6); assertThat(itineraries.getValues().stream().map(Itinerary::getName).toList()) .containsExactly("day 1", "day 2", "day 3", "day 4", "day 5", "day 6"); @@ -193,32 +183,32 @@ void updatePeriodBetweenPrePeriodStartBeforeAndPrePeriodEndAfter() { LocalDate.now().plusDays(5), LocalDate.now().plusDays(6), LocalDate.now().plusDays(7), - LocalDate.now().plusDays(8) - ); + LocalDate.now().plusDays(8)); } @Test @DisplayName("변경 일자가 '이전 일정 시작 후 ~ 이전 일정 종료 전'으로 변경할 수 있다") void updatePeriodBetweenPrePeriodStartAfterAndPrePeriodEndBefore() { - //given - TripPeriod period = new TripPeriod(LocalDate.now().plusDays(5), LocalDate.now().plusDays(10)); - Trip trip = Trip.createWithItineraries( - memberId, - UUID.randomUUID(), - new TripTitle("속초 여행 멤버 구함"), - new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), - period, - true, - 4, - TripCategory.DOMESTIC - ); + // given + TripPeriod period = + new TripPeriod(LocalDate.now().plusDays(5), LocalDate.now().plusDays(10)); + Trip trip = + Trip.createWithItineraries( + UUID.randomUUID(), + new TripTitle("속초 여행 멤버 구함"), + new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), + period, + true, + 4, + TripCategory.DOMESTIC); Itineraries itineraries = new Itineraries(trip, period); - //when - TripPeriod updatePeriod = new TripPeriod(LocalDate.now().plusDays(7), LocalDate.now().plusDays(9)); + // when + TripPeriod updatePeriod = + new TripPeriod(LocalDate.now().plusDays(7), LocalDate.now().plusDays(9)); itineraries.updateByPeriod(updatePeriod, trip); - //then + // then assertThat(itineraries.getValues()).hasSize(3); assertThat(itineraries.getValues().stream().map(Itinerary::getName).toList()) .containsExactly("day 1", "day 2", "day 3"); @@ -226,32 +216,32 @@ void updatePeriodBetweenPrePeriodStartAfterAndPrePeriodEndBefore() { .containsExactly( LocalDate.now().plusDays(7), LocalDate.now().plusDays(8), - LocalDate.now().plusDays(9) - ); + LocalDate.now().plusDays(9)); } @Test @DisplayName("변경 일자가 '이전 일정 시작 후 ~ 이전 일정 종료 후'으로 변경할 수 있다") void updatePeriodBetweenPrePeriodStartAfterAndPrePeriodEndAfter() { - //given - TripPeriod period = new TripPeriod(LocalDate.now().plusDays(5), LocalDate.now().plusDays(10)); - Trip trip = Trip.createWithItineraries( - memberId, - UUID.randomUUID(), - new TripTitle("속초 여행 멤버 구함"), - new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), - period, - true, - 4, - TripCategory.DOMESTIC - ); + // given + TripPeriod period = + new TripPeriod(LocalDate.now().plusDays(5), LocalDate.now().plusDays(10)); + Trip trip = + Trip.createWithItineraries( + UUID.randomUUID(), + new TripTitle("속초 여행 멤버 구함"), + new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), + period, + true, + 4, + TripCategory.DOMESTIC); Itineraries itineraries = new Itineraries(trip, period); - //when - TripPeriod updatePeriod = new TripPeriod(LocalDate.now().plusDays(7), LocalDate.now().plusDays(12)); + // when + TripPeriod updatePeriod = + new TripPeriod(LocalDate.now().plusDays(7), LocalDate.now().plusDays(12)); itineraries.updateByPeriod(updatePeriod, trip); - //then + // then assertThat(itineraries.getValues()).hasSize(6); assertThat(itineraries.getValues().stream().map(Itinerary::getName).toList()) .containsExactly("day 1", "day 2", "day 3", "day 4", "day 5", "day 6"); @@ -262,32 +252,32 @@ void updatePeriodBetweenPrePeriodStartAfterAndPrePeriodEndAfter() { LocalDate.now().plusDays(9), LocalDate.now().plusDays(10), LocalDate.now().plusDays(11), - LocalDate.now().plusDays(12) - ); + LocalDate.now().plusDays(12)); } @Test @DisplayName("변경 일자가 '이전 일정 종료 후'으로 변경할 수 있다") void updatePeriodIsAfterPrePeriodEndAfter() { - //given - TripPeriod period = new TripPeriod(LocalDate.now().plusDays(5), LocalDate.now().plusDays(10)); - Trip trip = Trip.createWithItineraries( - memberId, - UUID.randomUUID(), - new TripTitle("속초 여행 멤버 구함"), - new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), - period, - true, - 4, - TripCategory.DOMESTIC - ); + // given + TripPeriod period = + new TripPeriod(LocalDate.now().plusDays(5), LocalDate.now().plusDays(10)); + Trip trip = + Trip.createWithItineraries( + UUID.randomUUID(), + new TripTitle("속초 여행 멤버 구함"), + new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), + period, + true, + 4, + TripCategory.DOMESTIC); Itineraries itineraries = new Itineraries(trip, period); - //when - TripPeriod updatePeriod = new TripPeriod(LocalDate.now().plusDays(11), LocalDate.now().plusDays(14)); + // when + TripPeriod updatePeriod = + new TripPeriod(LocalDate.now().plusDays(11), LocalDate.now().plusDays(14)); itineraries.updateByPeriod(updatePeriod, trip); - //then + // then assertThat(itineraries.getValues()).hasSize(4); assertThat(itineraries.getValues().stream().map(Itinerary::getName).toList()) .containsExactly("day 1", "day 2", "day 3", "day 4"); @@ -296,31 +286,29 @@ void updatePeriodIsAfterPrePeriodEndAfter() { LocalDate.now().plusDays(11), LocalDate.now().plusDays(12), LocalDate.now().plusDays(13), - LocalDate.now().plusDays(14) - ); + LocalDate.now().plusDays(14)); } @Test @DisplayName("여행 상세 일정을 제거할 수 있다.") void deleteItineraryDetail() { - //given - TripPeriod period = new TripPeriod(LocalDate.now().plusDays(5), LocalDate.now().plusDays(10)); - Trip trip = Trip.createWithItineraries( - memberId, - UUID.randomUUID(), - new TripTitle("속초 여행 멤버 구함"), - new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), - period, - true, - 4, - TripCategory.DOMESTIC - ); - - //when + // given + TripPeriod period = + new TripPeriod(LocalDate.now().plusDays(5), LocalDate.now().plusDays(10)); + Trip trip = + Trip.createWithItineraries( + UUID.randomUUID(), + new TripTitle("속초 여행 멤버 구함"), + new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), + period, + true, + 4, + TripCategory.DOMESTIC); + + // when trip.getItineraries().getValues().getFirst().removeItineraryDetail(UUID.randomUUID()); - - //then + // then } } diff --git a/src/test/java/com/retrip/trip/domain/entity/ItineraryDetailsTest.java b/src/test/java/com/retrip/trip/domain/entity/ItineraryDetailsTest.java index e77cced..d27669e 100644 --- a/src/test/java/com/retrip/trip/domain/entity/ItineraryDetailsTest.java +++ b/src/test/java/com/retrip/trip/domain/entity/ItineraryDetailsTest.java @@ -20,24 +20,22 @@ class ItineraryDetailsTest { @DisplayName("여행 상세 일정은 여행 일정과 일정이 같아야 한다.") @Test void ItineraryDetailsDayIsEqualToItineraryDay() { - TripPeriod period = new TripPeriod( - LocalDate.now().plusDays(1), - LocalDate.now().plusDays(5)); - Trip trip = Trip.createWithItineraries( - memberId, - UUID.randomUUID(), - new TripTitle("속초 여행 멤버 구함"), - new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), - period, - true, - 4, - TripCategory.DOMESTIC - ); + TripPeriod period = + new TripPeriod(LocalDate.now().plusDays(1), LocalDate.now().plusDays(5)); + Trip trip = + Trip.createWithItineraries( + UUID.randomUUID(), + new TripTitle("속초 여행 멤버 구함"), + new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), + period, + true, + 4, + TripCategory.DOMESTIC); Itineraries itineraries = new Itineraries(trip, period); Itinerary itinerary = itineraries.getValues().getFirst(); - ItineraryDetail itineraryDetail - = ItineraryDetail.create(2000L, "속초 여행", LocalDateTime.now(), itinerary, locationId); + ItineraryDetail itineraryDetail = + ItineraryDetail.create(2000L, "속초 여행", LocalDateTime.now(), itinerary, locationId); assertThatThrownBy(() -> itinerary.addItineraryDetail(itineraryDetail)) .isExactlyInstanceOf(IllegalArgumentException.class); @@ -46,28 +44,28 @@ void ItineraryDetailsDayIsEqualToItineraryDay() { @DisplayName("같은 시간에 상세 일정이 있으면 안된다.") @Test void canNotItineraryDetailsTimeConflicting() { - TripPeriod period = new TripPeriod( - LocalDate.now().plusDays(1), - LocalDate.now().plusDays(5)); - Trip trip = Trip.createWithItineraries( - memberId, - UUID.randomUUID(), - new TripTitle("속초 여행 멤버 구함"), - new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), - period, - true, - 4, - TripCategory.DOMESTIC - ); + TripPeriod period = + new TripPeriod(LocalDate.now().plusDays(1), LocalDate.now().plusDays(5)); + Trip trip = + Trip.createWithItineraries( + UUID.randomUUID(), + new TripTitle("속초 여행 멤버 구함"), + new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), + period, + true, + 4, + TripCategory.DOMESTIC); Itineraries itineraries = new Itineraries(trip, period); Itinerary itinerary = itineraries.getValues().getFirst(); - ItineraryDetail itineraryDetail - = ItineraryDetail.create(2000L, "속초 여행", LocalDateTime.now().plusDays(1), itinerary, locationId); + ItineraryDetail itineraryDetail = + ItineraryDetail.create( + 2000L, "속초 여행", LocalDateTime.now().plusDays(1), itinerary, locationId); itinerary.addItineraryDetail(itineraryDetail); - ItineraryDetail itineraryDetail2 - = ItineraryDetail.create(2000L, "속초 여행", LocalDateTime.now().plusDays(1), itinerary, locationId); + ItineraryDetail itineraryDetail2 = + ItineraryDetail.create( + 2000L, "속초 여행", LocalDateTime.now().plusDays(1), itinerary, locationId); assertThatThrownBy(() -> itinerary.addItineraryDetail(itineraryDetail2)) .isExactlyInstanceOf(IllegalArgumentException.class); diff --git a/src/test/java/com/retrip/trip/domain/entity/ParticipantsTest.java b/src/test/java/com/retrip/trip/domain/entity/ParticipantsTest.java new file mode 100644 index 0000000..dcbcf4e --- /dev/null +++ b/src/test/java/com/retrip/trip/domain/entity/ParticipantsTest.java @@ -0,0 +1,100 @@ +package com.retrip.trip.domain.entity; + +import com.retrip.trip.domain.entity.participant.Participant; +import com.retrip.trip.domain.entity.participant.Participants; +import com.retrip.trip.domain.exception.ParticipantFullException; +import com.retrip.trip.domain.exception.common.InvalidValueException; +import com.retrip.trip.domain.fixture.ParticipantFixture; +import org.junit.jupiter.api.Test; + +import java.util.UUID; + +import static com.retrip.trip.domain.fixture.TripFixture.*; +import static org.junit.jupiter.api.Assertions.*; + +class ParticipantsTest { + + @Test + public void 리더가_아니라면_업데이트를_할_수_없다() { + // given + Trip trip = createTrip(TRIP_ID); + Participants participants = new Participants(LEADER_ID, trip, 4); + participants.addParticipant(Participant.createTripParticipant(MEMBER_ID, trip)); + + // when + boolean result = participants.updatableByLeader(MEMBER_ID); + + // then + assertFalse(result); + } + + @Test + void 최대_참여_인원보다_많은_인원이_참여할_수_없다() { + // given + Trip trip = createTrip(TRIP_ID); + Participants participants = new Participants(LEADER_ID, trip, 2); + UUID member1 = UUID.fromString("33333333-3333-3333-3333-333333333333"); + + // when + participants.addParticipant(Participant.createTripParticipant(member1, trip)); + + // then + assertThrows( + ParticipantFullException.class, + () -> { + UUID member2 = UUID.fromString("44444444-4444-4444-4444-444444444444"); + participants.addParticipant(Participant.createTripParticipant(member2, trip)); + }); + } + + @Test + void 최대_참여_인원을_현재_참여_인원보다_적게_변경할_수_없다() { + // given + Trip trip = createTrip(TRIP_ID); + Participants participants = new Participants(LEADER_ID, trip, 3); + UUID member1 = UUID.fromString("33333333-3333-3333-3333-333333333333"); + participants.addParticipant(Participant.createTripParticipant(member1, trip)); + + // then + assertThrows( + InvalidValueException.class, + () -> participants.updateMaxParticipants(1, LEADER_ID)); + } + + @Test + void 최대_참여_인원은_1명_이상이어야_한다() { + // given + Trip trip = createTrip(TRIP_ID); + + // then + assertThrows(InvalidValueException.class, () -> new Participants(LEADER_ID, trip, 0)); + } + + @Test + void 리더가_최대_참여_인원을_변경할_수_있다() { + // given + Trip trip = createTrip(TRIP_ID); + Participants participants = new Participants(LEADER_ID, trip, 3); + + // when + participants.updateMaxParticipants(5, LEADER_ID); + + // then + assertEquals(5, trip.getMaxParticipants()); + } + + @Test + void 현재_참여_인원수를_정확히_반환한다() { + // given + Trip trip = createTrip(TRIP_ID); + Participants participants = new Participants(LEADER_ID, trip, 3); + UUID member1 = UUID.fromString("33333333-3333-3333-3333-333333333333"); + participants.addParticipant(Participant.createTripParticipant(member1, trip)); + + // when + int currentCount = participants.getCurrentCount(); + + // then + assertEquals(2, currentCount); // 리더 + 참여자 1명 + } +} diff --git a/src/test/java/com/retrip/trip/domain/entity/TripParticipantsTest.java b/src/test/java/com/retrip/trip/domain/entity/TripParticipantsTest.java deleted file mode 100644 index b418448..0000000 --- a/src/test/java/com/retrip/trip/domain/entity/TripParticipantsTest.java +++ /dev/null @@ -1,120 +0,0 @@ -package com.retrip.trip.domain.entity; - -import com.retrip.trip.domain.exception.TripFullException; -import com.retrip.trip.domain.exception.common.InvalidValueException; -import org.junit.jupiter.api.Test; - -import java.util.UUID; - -import static com.retrip.trip.domain.fixture.TripFixture.*; -import static org.junit.jupiter.api.Assertions.*; - -class TripParticipantsTest { - - @Test - public void 리더만_업데이트를_할_수_있다() { - //given - Trip trip = createTrip(TRIP_ID); - TripParticipants tripParticipants = new TripParticipants(LEADER_ID, trip, 4); - - //when - boolean result = tripParticipants.updatableByLeader(LEADER_ID); - - //then - assertTrue(result); - } - - @Test - public void 여행에_포함된_맴버가_아니라면_업데이트를_할_수_없다() { - //given - Trip trip = createTrip(TRIP_ID); - TripParticipants tripParticipants = new TripParticipants(LEADER_ID, trip, 4); - - //when, then - assertThrows(InvalidValueException.class, () -> tripParticipants.updatableByLeader(MEMBER_ID)); - - } - - @Test - public void 리더가_아니라면_업데이트를_할_수_없다() { - //given - Trip trip = createTrip(TRIP_ID); - TripParticipants tripParticipants = new TripParticipants(LEADER_ID, trip, 4); - tripParticipants.addParticipant(TripParticipant.createTripParticipant(MEMBER_ID, trip)); - - //when - boolean result = tripParticipants.updatableByLeader(MEMBER_ID); - - //then - assertFalse(result); - } - @Test - void 최대_참여_인원보다_많은_인원이_참여할_수_없다() { - // given - Trip trip = createTrip(TRIP_ID); - TripParticipants tripParticipants = new TripParticipants(LEADER_ID, trip, 2); - UUID member1 = UUID.fromString("33333333-3333-3333-3333-333333333333"); - - // when - tripParticipants.addParticipant(TripParticipant.createTripParticipant(member1, trip)); - - // then - assertThrows(TripFullException.class, () -> { - UUID member2 = UUID.fromString("44444444-4444-4444-4444-444444444444"); - tripParticipants.addParticipant(TripParticipant.createTripParticipant(member2, trip)); - }); - } - - @Test - void 최대_참여_인원을_현재_참여_인원보다_적게_변경할_수_없다() { - // given - Trip trip = createTrip(TRIP_ID); - TripParticipants tripParticipants = new TripParticipants(LEADER_ID, trip, 3); - UUID member1 = UUID.fromString("33333333-3333-3333-3333-333333333333"); - tripParticipants.addParticipant(TripParticipant.createTripParticipant(member1, trip)); - - // then - assertThrows(InvalidValueException.class, () -> - tripParticipants.updateMaxParticipants(1, LEADER_ID) - ); - } - - @Test - void 최대_참여_인원은_1명_이상이어야_한다() { - // given - Trip trip = createTrip(TRIP_ID); - - // then - assertThrows(InvalidValueException.class, () -> - new TripParticipants(LEADER_ID, trip, 0) - ); - } - - @Test - void 리더가_최대_참여_인원을_변경할_수_있다() { - // given - Trip trip = createTrip(TRIP_ID); - TripParticipants tripParticipants = new TripParticipants(LEADER_ID, trip, 3); - - // when - tripParticipants.updateMaxParticipants(5,LEADER_ID); - - // then - assertEquals(5, tripParticipants.getMaxParticipants()); - } - - @Test - void 현재_참여_인원수를_정확히_반환한다() { - // given - Trip trip = createTrip(TRIP_ID); - TripParticipants tripParticipants = new TripParticipants(LEADER_ID, trip, 3); - UUID member1 = UUID.fromString("33333333-3333-3333-3333-333333333333"); - tripParticipants.addParticipant(TripParticipant.createTripParticipant(member1, trip)); - - // when - int currentCount = tripParticipants.getCurrentCount(); - - // then - assertEquals(2, currentCount); // 리더 + 참여자 1명 - } -} diff --git a/src/test/java/com/retrip/trip/domain/entity/TripTest.java b/src/test/java/com/retrip/trip/domain/entity/TripTest.java index 960be34..ecbda55 100644 --- a/src/test/java/com/retrip/trip/domain/entity/TripTest.java +++ b/src/test/java/com/retrip/trip/domain/entity/TripTest.java @@ -1,5 +1,6 @@ package com.retrip.trip.domain.entity; +import com.retrip.trip.domain.entity.participant.Participant; import com.retrip.trip.domain.exception.PeriodUpdateFailedException; import com.retrip.trip.domain.vo.ParticipantRole; import com.retrip.trip.domain.vo.TripCategory; @@ -27,83 +28,81 @@ class TripTest { @DisplayName("제목, 설명, 여행지, 기간, 공개 여부, 참가인원수,카테고리를 입력해 여행을 생성할 수 있다.") @Test void create() { - assertThatCode(() -> Trip.create( - memberId, - destinationId, - new TripTitle("속초 여행 멤버 구함"), - new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), - new TripPeriod( - LocalDate.now().plusDays(1), - LocalDate.now().plusDays(5)), - true, - 4, - TripCategory.DOMESTIC - )).doesNotThrowAnyException(); + assertThatCode( + () -> + Trip.create( + destinationId, + new TripTitle("속초 여행 멤버 구함"), + new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), + new TripPeriod( + LocalDate.now().plusDays(1), + LocalDate.now().plusDays(5)), + true, + 4, + TripCategory.DOMESTIC)) + .doesNotThrowAnyException(); } @DisplayName("제목, 여행지, 기간, 공개 여부를 입력해 여행과 일정 목록을 생성할 수 있다.") @Test void createWithItinerary() { - assertThatCode(() -> Trip.createWithItineraries( - memberId, - destinationId, - new TripTitle("속초 여행 멤버 구함"), - new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), - new TripPeriod( - LocalDate.now().plusDays(1), - LocalDate.now().plusDays(5)), - true, - 4, - TripCategory.DOMESTIC - )).doesNotThrowAnyException(); + assertThatCode( + () -> + Trip.createWithItineraries( + destinationId, + new TripTitle("속초 여행 멤버 구함"), + new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), + new TripPeriod( + LocalDate.now().plusDays(1), + LocalDate.now().plusDays(5)), + true, + 4, + TripCategory.DOMESTIC)) + .doesNotThrowAnyException(); } @Test void 여행을_생성하면_생성자는_해당_여행에_리더가_된다() { // given - Trip trip = Trip.create( - memberId, - destinationId, - new TripTitle("속초 여행 멤버 구함"), - new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), - new TripPeriod( - LocalDate.now().plusDays(1), - LocalDate.now().plusDays(5)), - true, - 4, - TripCategory.DOMESTIC - ); + Trip trip = + Trip.create( + destinationId, + new TripTitle("속초 여행 멤버 구함"), + new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), + new TripPeriod(LocalDate.now().plusDays(1), LocalDate.now().plusDays(5)), + true, + 4, + TripCategory.DOMESTIC); // when - List participants = trip.getTripParticipants().getValues(); + List participants = trip.getParticipants().getValues(); // then assertAll( () -> assertThat(participants).hasSize(1), () -> assertThat(participants.get(0).getRole()).isEqualTo(ParticipantRole.LEADER), - () -> assertThat(participants.get(0).getMemberId()).isEqualTo(memberId) - ); + () -> assertThat(participants.get(0).getMemberId()).isEqualTo(memberId)); } @Test void 여행_일정이_없다면_균등_일정_생성으로_업데이트가_가능하다() { - TripPeriod period = new TripPeriod(LocalDate.now().plusDays(1), LocalDate.now().plusDays(6)); - Trip trip = Trip.create( - memberId, - UUID.randomUUID(), - new TripTitle("속초 여행 멤버 구함"), - new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), - period, - true, - 4, - TripCategory.DOMESTIC - ); - //when - TripPeriod updateTripPeriod = new TripPeriod(LocalDate.now().plusDays(2), LocalDate.now().plusDays(4)); + TripPeriod period = + new TripPeriod(LocalDate.now().plusDays(1), LocalDate.now().plusDays(6)); + Trip trip = + Trip.create( + UUID.randomUUID(), + new TripTitle("속초 여행 멤버 구함"), + new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), + period, + true, + 4, + TripCategory.DOMESTIC); + // when + TripPeriod updateTripPeriod = + new TripPeriod(LocalDate.now().plusDays(2), LocalDate.now().plusDays(4)); trip.updatePeriod(updateTripPeriod, memberId); - - //then + // then assertThat(trip.getPeriod().getStart()).isEqualTo(updateTripPeriod.getStart()); assertThat(trip.getPeriod().getEnd()).isEqualTo(updateTripPeriod.getEnd()); assertThat(trip.getItineraries().getValues().size()).isEqualTo(3); @@ -112,23 +111,26 @@ void createWithItinerary() { @Test void 여행_일정은_리더만_수정_가능하다() { - TripPeriod period = new TripPeriod(LocalDate.now().plusDays(1), LocalDate.now().plusDays(6)); - Trip trip = Trip.create( - UUID.randomUUID(), - UUID.randomUUID(), - new TripTitle("속초 여행 멤버 구함"), - new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), - period, - true, - 4, - TripCategory.DOMESTIC - ); - trip.addParticipant(TripParticipant.createTripParticipant(memberId, trip)); - - //when - TripPeriod updateTripPeriod = new TripPeriod(LocalDate.now().plusDays(2), LocalDate.now().plusDays(4)); - - //then - assertThrows(PeriodUpdateFailedException.class, () -> trip.updatePeriod(updateTripPeriod, memberId)); + TripPeriod period = + new TripPeriod(LocalDate.now().plusDays(1), LocalDate.now().plusDays(6)); + Trip trip = + Trip.create( + UUID.randomUUID(), + new TripTitle("속초 여행 멤버 구함"), + new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), + period, + true, + 4, + TripCategory.DOMESTIC); + trip.addParticipant(Participant.createTripParticipant(memberId, trip)); + + // when + TripPeriod updateTripPeriod = + new TripPeriod(LocalDate.now().plusDays(2), LocalDate.now().plusDays(4)); + + // then + assertThrows( + PeriodUpdateFailedException.class, + () -> trip.updatePeriod(updateTripPeriod, memberId)); } } diff --git a/src/test/java/com/retrip/trip/domain/fixture/ParticipantFixture.java b/src/test/java/com/retrip/trip/domain/fixture/ParticipantFixture.java new file mode 100644 index 0000000..fa103ed --- /dev/null +++ b/src/test/java/com/retrip/trip/domain/fixture/ParticipantFixture.java @@ -0,0 +1,23 @@ +package com.retrip.trip.domain.fixture; + +import com.retrip.trip.domain.entity.participant.Participant; +import com.retrip.trip.domain.vo.*; + +import java.util.UUID; + +public class ParticipantFixture { + public static final UUID LEADER_ID = UUID.fromString("caec62d1-f29d-477d-9743-292f48cc66bb"); + public static final UUID 정수_ID = UUID.fromString("a7f7215b-081a-42f4-b3e2-f06393de2f8b"); + public static final UUID 홍석_ID = UUID.fromString("bf97d20b-d1f7-46a9-8362-11b9fa02d67d"); + public static final UUID 준호_ID = UUID.fromString("8b9b67fd-1d88-4b30-bfea-cd8f89fc10d9"); + public static final UUID 지수_ID = UUID.fromString("de3b60d2-5672-464d-8769-bf5c9de5eaff"); + public static final UUID 혁진_ID = UUID.fromString("42880aaf-4b97-4b0c-8a8a-72df4bb592f6"); + + public static Participant createLeaderParticipant(UUID tripId, UUID leaderId) { + return Participant.create(tripId, leaderId, ParticipantRole.LEADER); + } + + public static Participant createParticipant(UUID tripId, UUID leaderId) { + return Participant.create(tripId, leaderId, ParticipantRole.PARTICIPANT); + } +} diff --git a/src/test/java/com/retrip/trip/domain/fixture/TripFixture.java b/src/test/java/com/retrip/trip/domain/fixture/TripFixture.java index 3e5d44f..e63e704 100644 --- a/src/test/java/com/retrip/trip/domain/fixture/TripFixture.java +++ b/src/test/java/com/retrip/trip/domain/fixture/TripFixture.java @@ -21,23 +21,22 @@ public class TripFixture { public static final UUID 혁진_ID = UUID.fromString("42880aaf-4b97-4b0c-8a8a-72df4bb592f6"); public static Trip createTrip(UUID tripId) { - Trip trip = Trip.createWithItineraries( - LEADER_ID, - UUID.randomUUID(), - new TripTitle("속초 여행 멤버 구함"), - new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), - new TripPeriod(LocalDate.now().plusDays(1), LocalDate.now().plusDays(10)), - true, - 4, - TripCategory.DOMESTIC); + Trip trip = + Trip.createWithItineraries( + UUID.randomUUID(), + new TripTitle("속초 여행 멤버 구함"), + new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), + new TripPeriod(LocalDate.now().plusDays(1), LocalDate.now().plusDays(10)), + true, + 4, + TripCategory.DOMESTIC); ReflectionTestUtils.setField(trip, "id", tripId); return trip; } - public static Trip createTestTrip(UUID memberId, String title, String description, TripCategory category) { + public static Trip createTestTrip(String title, String description, TripCategory category) { TripPeriod period = createFuturePeriod(); return Trip.create( - memberId, UUID.randomUUID(), new TripTitle(title), new TripDescription(description), diff --git a/src/test/java/com/retrip/trip/domain/service/InvitationPolicyTest.java b/src/test/java/com/retrip/trip/domain/service/InvitationPolicyTest.java index 4cfea00..4202d6b 100644 --- a/src/test/java/com/retrip/trip/domain/service/InvitationPolicyTest.java +++ b/src/test/java/com/retrip/trip/domain/service/InvitationPolicyTest.java @@ -1,11 +1,12 @@ package com.retrip.trip.domain.service; +import com.retrip.trip.domain.entity.participant.Participant; import com.retrip.trip.domain.entity.Trip; -import com.retrip.trip.domain.entity.TripParticipant; import com.retrip.trip.domain.entity.invitation.Invitation; import com.retrip.trip.domain.exception.*; import com.retrip.trip.domain.exception.common.IllegalStateException; import com.retrip.trip.domain.vo.InvitationStatus; +import com.retrip.trip.domain.vo.ParticipantRole; import com.retrip.trip.domain.vo.TripStatus; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -30,21 +31,21 @@ class InvitationPolicyTest { void 리더가_아닌_멤버가_사용자를_초대하면_예외가_발생한다() { // given Trip trip = createTrip(TRIP_ID); - TripParticipant participant = TripParticipant.createTripParticipant(혁진_ID, trip); - trip.addParticipant(participant); + Participant participant = Participant.create(trip.getId(), 혁진_ID, ParticipantRole.PARTICIPANT) + List memberIds = List.of(정수_ID, 홍석_ID, 준호_ID); // when, then assertThatThrownBy(() -> invitationPolicy.canInvite(trip, 혁진_ID, memberIds)) - .isExactlyInstanceOf(MemberIsNotLeaderException.class); + .isExactlyInstanceOf(NotLeaderException.class); } @Test void 이미_여행_멤버인_사용자를_초대하면_예외가_발생한다() { // given Trip trip = createTrip(TRIP_ID); - trip.addParticipant(TripParticipant.createTripParticipant(혁진_ID, trip)); - trip.addParticipant(TripParticipant.createTripParticipant(지수_ID, trip)); + trip.addParticipant(Participant.createTripParticipant(혁진_ID, trip)); + trip.addParticipant(Participant.createTripParticipant(지수_ID, trip)); List memberIds = List.of(혁진_ID); // when, then @@ -53,11 +54,9 @@ class InvitationPolicyTest { } @ParameterizedTest - @EnumSource(mode = INCLUDE, names = { - "BEFORE_TRIP", - "IN_PROGRESS", - "COMPLETED" - }) + @EnumSource( + mode = INCLUDE, + names = {"BEFORE_TRIP", "IN_PROGRESS", "COMPLETED"}) void 여행이_모집중이거나_모집완료인_경우에만_초대를_생성할_수_있다(TripStatus status) { // given Trip trip = createTrip(TRIP_ID); @@ -96,7 +95,9 @@ class InvitationPolicyTest { } @ParameterizedTest - @EnumSource(mode = EXCLUDE, names = {"RECRUITING"}) + @EnumSource( + mode = EXCLUDE, + names = {"RECRUITING"}) void 여행이_모집중이_아니면_수락되지_않는다(TripStatus status) { // given Trip trip = createTrip(TRIP_ID); @@ -112,9 +113,9 @@ class InvitationPolicyTest { void 여행이_최대인원이_채워진_경우_수락되지_않는다() { // given Trip trip = createTrip(TRIP_ID); - trip.addParticipant(TripParticipant.createTripParticipant(혁진_ID, trip)); - trip.addParticipant(TripParticipant.createTripParticipant(지수_ID, trip)); - trip.addParticipant(TripParticipant.createTripParticipant(준호_ID, trip)); + trip.addParticipant(Participant.createTripParticipant(혁진_ID, trip)); + trip.addParticipant(Participant.createTripParticipant(지수_ID, trip)); + trip.addParticipant(Participant.createTripParticipant(준호_ID, trip)); Invitation invitation = new Invitation(TRIP_ID, MEMBER_ID); @@ -124,7 +125,9 @@ class InvitationPolicyTest { } @ParameterizedTest - @EnumSource(mode = EXCLUDE, names = {"INVITED"}) + @EnumSource( + mode = EXCLUDE, + names = {"INVITED"}) void 초대_상태가_아니면_거절할_수_없다(InvitationStatus status) { // given Invitation invitation = new Invitation(TRIP_ID, MEMBER_ID); diff --git a/src/test/java/com/retrip/trip/domain/service/ParticipantPolicyTest.java b/src/test/java/com/retrip/trip/domain/service/ParticipantPolicyTest.java new file mode 100644 index 0000000..f08dafe --- /dev/null +++ b/src/test/java/com/retrip/trip/domain/service/ParticipantPolicyTest.java @@ -0,0 +1,39 @@ +package com.retrip.trip.domain.service; + +import static com.retrip.trip.domain.fixture.TripFixture.*; +import static com.retrip.trip.domain.fixture.TripFixture.TRIP_ID; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.retrip.trip.domain.entity.Trip; +import com.retrip.trip.domain.entity.participant.Participant; +import com.retrip.trip.domain.exception.NotLeaderException; +import com.retrip.trip.domain.exception.ParticipantFullException; +import com.retrip.trip.domain.fixture.ParticipantFixture; + +import org.junit.jupiter.api.Test; + +class ParticipantPolicyTest { + ParticipantPolicy participantPolicy = new ParticipantPolicy(); + + @Test + void 여행_최대인원보다_많이_참여할_수_없다() { + // given + Trip trip = createTrip(TRIP_ID); + ; + // when, then + assertThatThrownBy(() -> participantPolicy.validate(trip.getMaxParticipants(), 4L)) + .isExactlyInstanceOf(ParticipantFullException.class); + } + + @Test + void 리더가_아니면_업데이트를_할_수_없다() { + // given + Trip trip = createTrip(TRIP_ID); + Participant participant = ParticipantFixture.createParticipant(trip.getId(), MEMBER_ID); + + // when, then + assertThatThrownBy(() -> participantPolicy.validateLeader(participant.getRole())) + .isExactlyInstanceOf(NotLeaderException.class); + } +} From 01b97b992e60f5db64b6e5730a6b246f6c6f7dca Mon Sep 17 00:00:00 2001 From: junhokim Date: Tue, 29 Jul 2025 10:08:29 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat:=202=EC=B0=A8=20=EB=A6=AC=ED=8E=99?= =?UTF-8?q?=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 테스트 코드 수정 필요 --- .../request/MaxParticipantUpdateRequest.java | 10 + .../in/response/DelegateLeaderResponse.java | 19 +- .../MaxParticipantUpdateResponse.java | 16 ++ .../in/service/InvitationService.java | 16 +- .../in/service/ParticipantService.java | 74 +++++- .../application/in/service/TripService.java | 46 ++-- .../in/usecase/ParticipantManageUseCase.java | 14 +- .../in/usecase/TripManagementUseCase.java | 13 ++ .../out/repository/ParticipantRepository.java | 4 +- .../out/repository/TripRepository.java | 5 +- .../com/retrip/trip/domain/entity/Trip.java | 40 +--- .../retrip/trip/domain/entity/TripDemand.java | 6 - .../entity/participant/Participant.java | 14 -- .../entity/participant/Participants.java | 128 ----------- .../exception/LeaderCannotLeaveException.java | 12 - .../trip/domain/service/InvitationPolicy.java | 29 +-- .../domain/service/ParticipantPolicy.java | 52 +++++ .../trip/domain/vo/ParticipantRole.java | 5 +- .../trip/domain/vo/ParticipantStatus.java | 6 +- .../in/presentation/rest/TripController.java | 10 + .../in/ParticipantServiceTest.java | 125 ++++++++++- .../trip/application/in/TripServiceTest.java | 211 +++++++++--------- .../in/base/BaseInvitationServiceTest.java | 28 ++- .../trip/domain/entity/ParticipantsTest.java | 100 --------- .../trip/domain/entity/TripDemandTest.java | 0 .../retrip/trip/domain/entity/TripTest.java | 63 +----- .../entity/participant/ParticipantTest.java | 31 +++ .../domain/fixture/ParticipantFixture.java | 4 +- .../domain/service/InvitationPolicyTest.java | 44 +--- .../domain/service/ParticipantPolicyTest.java | 114 +++++++++- 30 files changed, 649 insertions(+), 590 deletions(-) create mode 100644 src/main/java/com/retrip/trip/application/in/request/MaxParticipantUpdateRequest.java create mode 100644 src/main/java/com/retrip/trip/application/in/response/MaxParticipantUpdateResponse.java create mode 100644 src/main/java/com/retrip/trip/application/in/usecase/TripManagementUseCase.java delete mode 100644 src/main/java/com/retrip/trip/domain/entity/participant/Participants.java delete mode 100644 src/main/java/com/retrip/trip/domain/exception/LeaderCannotLeaveException.java delete mode 100644 src/test/java/com/retrip/trip/domain/entity/ParticipantsTest.java delete mode 100644 src/test/java/com/retrip/trip/domain/entity/TripDemandTest.java create mode 100644 src/test/java/com/retrip/trip/domain/entity/participant/ParticipantTest.java diff --git a/src/main/java/com/retrip/trip/application/in/request/MaxParticipantUpdateRequest.java b/src/main/java/com/retrip/trip/application/in/request/MaxParticipantUpdateRequest.java new file mode 100644 index 0000000..4c50cbc --- /dev/null +++ b/src/main/java/com/retrip/trip/application/in/request/MaxParticipantUpdateRequest.java @@ -0,0 +1,10 @@ +package com.retrip.trip.application.in.request; + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.UUID; + +@Schema(description = "여행 최대 참여자 수정 Request") +public record MaxParticipantUpdateRequest( + @Schema(description = "여행 최대 참여자") int maxParticipants, + @Schema(description = "참여자 수정 ID") UUID memberId) {} diff --git a/src/main/java/com/retrip/trip/application/in/response/DelegateLeaderResponse.java b/src/main/java/com/retrip/trip/application/in/response/DelegateLeaderResponse.java index 9c26411..1021ca3 100644 --- a/src/main/java/com/retrip/trip/application/in/response/DelegateLeaderResponse.java +++ b/src/main/java/com/retrip/trip/application/in/response/DelegateLeaderResponse.java @@ -1,18 +1,19 @@ package com.retrip.trip.application.in.response; import com.retrip.trip.domain.entity.Trip; +import com.retrip.trip.domain.entity.participant.Participant; +import com.retrip.trip.domain.vo.ParticipantRole; import io.swagger.v3.oas.annotations.media.Schema; import java.util.UUID; @Schema(description = "여행 리더 위임 Response") public record DelegateLeaderResponse( - @Schema(description = "여행 ID") - UUID tripId, - - @Schema(description = "새로운 리더 멤버 ID") - UUID newLeaderId -) { - public static DelegateLeaderResponse of(Trip trip, UUID newLeaderId) { - return new DelegateLeaderResponse(trip.getId(), newLeaderId); + @Schema(description = "새로운 리더 ID") UUID id, + @Schema(description = "여행 ID") UUID tripId, + @Schema(description = "새로운 리더 멤버 ID") UUID memberId, + @Schema(description = "새로운 리더 멤버 Role") String role) { + public static DelegateLeaderResponse of(Trip trip, Participant leader) { + return new DelegateLeaderResponse( + leader.getId(), trip.getId(), leader.getMemberId(), leader.getRole().getViewName()); } -} \ No newline at end of file +} diff --git a/src/main/java/com/retrip/trip/application/in/response/MaxParticipantUpdateResponse.java b/src/main/java/com/retrip/trip/application/in/response/MaxParticipantUpdateResponse.java new file mode 100644 index 0000000..92341d1 --- /dev/null +++ b/src/main/java/com/retrip/trip/application/in/response/MaxParticipantUpdateResponse.java @@ -0,0 +1,16 @@ +package com.retrip.trip.application.in.response; + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.time.LocalDate; +import java.util.UUID; + +@Schema(description = "여행 최대 참여자 수정 Response") +public record MaxParticipantUpdateResponse( + @Schema(description = "여행 Id") UUID tripId, + @Schema(description = "여행 최대 참여자") int maxParticipants) { + + public static MaxParticipantUpdateResponse of(UUID id, int maxParticipants) { + return new MaxParticipantUpdateResponse(id, maxParticipants); + } +} diff --git a/src/main/java/com/retrip/trip/application/in/service/InvitationService.java b/src/main/java/com/retrip/trip/application/in/service/InvitationService.java index ada6b5d..ad6d89a 100644 --- a/src/main/java/com/retrip/trip/application/in/service/InvitationService.java +++ b/src/main/java/com/retrip/trip/application/in/service/InvitationService.java @@ -35,8 +35,10 @@ public class InvitationService implements InvitationManageUseCase { @Override public InvitationsCreateResponse createInvitations( UUID tripId, TripInvitationsCreateRequest request) { - Trip trip = findTripWithParticipants(tripId); - invitationPolicy.canInvite(trip, request.leaderId(), request.memberIds()); + Trip trip = tripRepository.findById(tripId).orElseThrow(EntityNotFoundException::new); + invitationPolicy.canInvite(trip); + participantService.canInvite(trip.getId(), request.leaderId(), request.memberIds()); + Invitations invitations = new Invitations(invitationRepository.findByTripId(tripId)); invitations.add(tripId, request.memberIds()); List savedInvitations = invitationRepository.saveAll(invitations.getValues()); @@ -52,7 +54,7 @@ public Page getTripInvitations( Pageable page, TripInvitationOrder order, String sort) { - invitationPolicy.canViewInvitations(findTripWithParticipants(tripId), leaderId); + participantService.requireLeaderOrElseThrow(tripId, leaderId); Pageable pageable = PaginationUtils.createPageRequest(page, order.getField(), sort); Page tripInvitations = invitationRepository.findByTripIdAndStatus( @@ -73,7 +75,7 @@ public Page getMemberInvitations( @Override public MemberInvitationAcceptResponse acceptMemberInvitations( UUID memberId, UUID tripId, UUID invitationId) { - Trip trip = findTripWithParticipants(tripId); + Trip trip = tripRepository.findById(tripId).orElseThrow(EntityNotFoundException::new); Invitation invitation = findInvitation(invitationId); invitationPolicy.canAccept(trip, invitation); invitation.accept(); @@ -90,12 +92,6 @@ public MemberInvitationRejectResponse rejectMemberInvitations( return MemberInvitationRejectResponse.of(invitation); } - private Trip findTripWithParticipants(UUID tripId) { - return tripRepository - .findWithParticipantsById(tripId) - .orElseThrow(EntityNotFoundException::new); - } - private Invitation findInvitation(UUID invitationId) { return invitationRepository .findById(invitationId) diff --git a/src/main/java/com/retrip/trip/application/in/service/ParticipantService.java b/src/main/java/com/retrip/trip/application/in/service/ParticipantService.java index 67119bf..6cff31c 100644 --- a/src/main/java/com/retrip/trip/application/in/service/ParticipantService.java +++ b/src/main/java/com/retrip/trip/application/in/service/ParticipantService.java @@ -6,6 +6,7 @@ import com.retrip.trip.application.out.repository.ParticipantRepository; import com.retrip.trip.domain.entity.participant.Participant; +import com.retrip.trip.domain.exception.NotParticipantException; import com.retrip.trip.domain.exception.common.EntityNotFoundException; import com.retrip.trip.domain.service.ParticipantPolicy; @@ -21,7 +22,7 @@ @Service @RequiredArgsConstructor -@Transactional(readOnly = true) +@Transactional public class ParticipantService implements ParticipantManageUseCase { private final ParticipantPolicy participantPolicy; private final ParticipantRepository participantRepository; @@ -34,11 +35,13 @@ public Participant createLeaderParticipant(UUID tripId, UUID memberId) { } @Override + @Transactional(readOnly = true) public List findByMemberId(UUID memberId, Pageable page) { return participantQueryRepository.findByMemberId(memberId, page); } @Override + @Transactional(readOnly = true) public Long findByMemberIdTotalCount(UUID memberId) { return participantQueryRepository.findByMemberId(memberId); } @@ -52,11 +55,78 @@ public Participant createParticipant(UUID tripId, UUID memberId, int maxParticip } @Override - public void updateByLeaderOrThrow(UUID tripId, UUID memberId) { + @Transactional(readOnly = true) + public void requireLeaderOrElseThrow(UUID tripId, UUID memberId) { Participant participant = participantQueryRepository .findByTripIdAndMemberId(tripId, memberId) .orElseThrow(EntityNotFoundException::new); participantPolicy.validateLeader(participant.getRole()); } + + @Override + @Transactional(readOnly = true) + public void requireParticipantOrElseThrow(UUID tripId, UUID memberId) { + Participant participant = + participantQueryRepository + .findByTripIdAndMemberId(tripId, memberId) + .orElseThrow(EntityNotFoundException::new); + participantPolicy.validateParticipant(participant.getRole()); + } + + @Override + public void remove(UUID tripId, UUID memberId) { + // todo: SoftDelete 변경 필요 + Participant participant = + participantQueryRepository + .findByTripIdAndMemberId(tripId, memberId) + .orElseThrow(EntityNotFoundException::new); + participantRepository.delete(participant); + } + + @Override + public Participant delegateLeader(UUID tripId, UUID currentLeaderId, UUID newLeaderId) { + + Participant oldLeader = + participantQueryRepository + .findByTripIdAndMemberId(tripId, currentLeaderId) + .orElseThrow(() -> new NotParticipantException("현재 리더를 찾을 수 없습니다.")); + Participant newLeader = + participantQueryRepository + .findByTripIdAndMemberId(tripId, newLeaderId) + .orElseThrow( + () -> + new NotParticipantException( + "새로운 리더가 될 멤버가 여행에 참여하고 있지 않습니다.")); + participantPolicy.validateDelegate(oldLeader, newLeader); + participantPolicy.changeLeader(oldLeader, newLeader); + return newLeader; + } + + @Override + public void canInvite(UUID tripId, UUID leaderId, List memberIds) { + requireLeaderOrElseThrow(tripId, leaderId); + List participants = participantRepository.findByTripId(tripId); + participantPolicy.validateInvite(participants, memberIds); + } + + @Override + public List banMembers(UUID tripId, UUID loginMemberId, List memberIds) { + requireLeaderOrElseThrow(tripId, loginMemberId); + List participants = participantRepository.findByTripId(tripId); + participantPolicy.validateBan(participants, memberIds); + List participantsToBan = + participants.stream().filter(m -> memberIds.contains(m.getMemberId())).toList(); + participantsToBan.forEach(Participant::ban); + return participantsToBan; + } + + @Override + public void canDemand(UUID tripId, UUID memberId) { + Participant participant = + participantQueryRepository + .findByTripIdAndMemberId(tripId, memberId) + .orElseThrow(EntityNotFoundException::new); + participantPolicy.validateDemand(participant); + } } diff --git a/src/main/java/com/retrip/trip/application/in/service/TripService.java b/src/main/java/com/retrip/trip/application/in/service/TripService.java index 3cfcf74..a778ae4 100644 --- a/src/main/java/com/retrip/trip/application/in/service/TripService.java +++ b/src/main/java/com/retrip/trip/application/in/service/TripService.java @@ -1,9 +1,6 @@ package com.retrip.trip.application.in.service; -import com.retrip.trip.application.in.request.DelegateLeaderRequest; -import com.retrip.trip.application.in.request.PeriodUpdateRequest; -import com.retrip.trip.application.in.request.TripCreateRequest; -import com.retrip.trip.application.in.request.TripDemandRequest; +import com.retrip.trip.application.in.request.*; import com.retrip.trip.application.in.response.*; import com.retrip.trip.application.in.usecase.*; import com.retrip.trip.application.out.repository.*; @@ -12,7 +9,6 @@ import com.retrip.trip.domain.entity.TripDemand; import com.retrip.trip.domain.entity.participant.Participant; import com.retrip.trip.domain.exception.TripNotFoundException; -import com.retrip.trip.domain.service.ParticipantPolicy; import com.retrip.trip.domain.vo.TripPeriod; import jakarta.persistence.EntityNotFoundException; @@ -37,13 +33,13 @@ public class TripService TripDemandUseCase, TripPeriodUseCase, LeaveTripUseCase, - DelegateLeaderUseCase { + DelegateLeaderUseCase, + TripManagementUseCase { private final TripRepository tripRepository; private final TripQueryRepository tripQueryRepository; private final TripItineraryQueryRepository tripItineraryQueryRepository; private final TripDemandReadRepository tripDemandReadRepository; private final ParticipantService participantService; - private final ParticipantPolicy participantPolicy; @Override public TripCreateResponse createTrip(TripCreateRequest request) { @@ -70,20 +66,19 @@ public Page getTrips(Pageable page) { @Override public TripDemandResponse tripDemand(UUID tripId, TripDemandRequest request) { Trip trip = findTrip(tripId); + participantService.canDemand(tripId, request.memberId()); trip.addDemand(TripDemand.create(request.memberId(), trip, request.message())); return TripDemandResponse.of(trip.getTripDemands().getValues().getLast()); } private Trip findTrip(UUID tripId) { - return tripRepository - .findWithParticipantsById(tripId) - .orElseThrow(TripNotFoundException::new); + return tripRepository.findById(tripId).orElseThrow(TripNotFoundException::new); } @Override public TripDemandApproveResponse approve(UUID memberId, UUID tripId, UUID tripDemandId) { TripDemand tripDemand = findTripDemandByTripIdAndTripDemandId(tripId, tripDemandId); - participantService.updateByLeaderOrThrow(tripId, memberId); + participantService.requireLeaderOrElseThrow(tripId, memberId); tripDemand.approve(); participantService.createParticipant( tripId, memberId, tripDemand.getTrip().getMaxParticipants()); @@ -93,7 +88,7 @@ public TripDemandApproveResponse approve(UUID memberId, UUID tripId, UUID tripDe @Override public TripDemandRejectResponse reject(UUID memberId, UUID tripId, UUID joinRequestId) { TripDemand tripDemand = findTripDemandByTripIdAndTripDemandId(tripId, joinRequestId); - participantService.updateByLeaderOrThrow(tripId, memberId); + participantService.requireLeaderOrElseThrow(tripId, memberId); tripDemand.reject(); return TripDemandRejectResponse.of(tripDemand); } @@ -114,7 +109,7 @@ public PeriodUpdateResponse updatePeriod(UUID tripId, PeriodUpdateRequest reques List itineraries = tripItineraryQueryRepository.findByIdsWithItineraryDetails( trip.getItinerariesIds()); - participantService.updateByLeaderOrThrow(tripId, request.memberId()); + participantService.requireLeaderOrElseThrow(tripId, request.memberId()); trip.updatePeriod(period, request.memberId()); return PeriodUpdateResponse.of(trip); } @@ -133,20 +128,35 @@ public Page getMyTrips(UUID memberId, Pageable page) { @Override public void banMembers(UUID loginMemberId, UUID tripId, List memberIds) { Trip trip = findTrip(tripId); - trip.banMembers(loginMemberId, memberIds); + trip.canBan(); + List participants = + participantService.banMembers(tripId, loginMemberId, memberIds); } @Override public void leaveTrip(UUID tripId, UUID memberId) { Trip trip = findTrip(tripId); - trip.leave(memberId); - tripRepository.save(trip); + trip.canLeave(); + participantService.requireParticipantOrElseThrow(tripId, memberId); + participantService.remove(tripId, memberId); } @Override public DelegateLeaderResponse delegateLeader(UUID tripId, DelegateLeaderRequest request) { Trip trip = findTrip(tripId); - trip.delegateLeader(request.currentLeaderId(), request.newLeaderId()); - return DelegateLeaderResponse.of(trip, request.newLeaderId()); + trip.canDelegateLeader(); + Participant participant = + participantService.delegateLeader( + tripId, request.currentLeaderId(), request.newLeaderId()); + return DelegateLeaderResponse.of(trip, participant); + } + + @Override + public MaxParticipantUpdateResponse updateMaxParticipants( + UUID tripId, MaxParticipantUpdateRequest request) { + Trip trip = findTrip(tripId); + participantService.requireLeaderOrElseThrow(tripId, request.memberId()); + trip.updateMaxParticipants(request.maxParticipants()); + return MaxParticipantUpdateResponse.of(trip.getId(), trip.getMaxParticipants()); } } diff --git a/src/main/java/com/retrip/trip/application/in/usecase/ParticipantManageUseCase.java b/src/main/java/com/retrip/trip/application/in/usecase/ParticipantManageUseCase.java index 0591583..5a6af05 100644 --- a/src/main/java/com/retrip/trip/application/in/usecase/ParticipantManageUseCase.java +++ b/src/main/java/com/retrip/trip/application/in/usecase/ParticipantManageUseCase.java @@ -16,5 +16,17 @@ public interface ParticipantManageUseCase { Participant createParticipant(UUID tripId, UUID memberId, int maxParticipants); - void updateByLeaderOrThrow(UUID tripId, UUID uuid); + void requireLeaderOrElseThrow(UUID tripId, UUID uuid); + + void requireParticipantOrElseThrow(UUID tripId, UUID uuid); + + void remove(UUID tripId, UUID memberId); + + Participant delegateLeader(UUID tripId, UUID currentLeaderId, UUID newLeaderId); + + void canInvite(UUID tripId, UUID leaderId, List memberIds); + + List banMembers(UUID tripId, UUID loginMemberId, List memberIds); + + void canDemand(UUID tripId, UUID memberId); } diff --git a/src/main/java/com/retrip/trip/application/in/usecase/TripManagementUseCase.java b/src/main/java/com/retrip/trip/application/in/usecase/TripManagementUseCase.java new file mode 100644 index 0000000..61cf8da --- /dev/null +++ b/src/main/java/com/retrip/trip/application/in/usecase/TripManagementUseCase.java @@ -0,0 +1,13 @@ +package com.retrip.trip.application.in.usecase; + +import com.retrip.trip.application.in.request.MaxParticipantUpdateRequest; +import com.retrip.trip.application.in.request.PeriodUpdateRequest; + +import com.retrip.trip.application.in.response.MaxParticipantUpdateResponse; + +import java.util.UUID; + +public interface TripManagementUseCase { + MaxParticipantUpdateResponse updateMaxParticipants( + UUID tripId, MaxParticipantUpdateRequest request); +} diff --git a/src/main/java/com/retrip/trip/application/out/repository/ParticipantRepository.java b/src/main/java/com/retrip/trip/application/out/repository/ParticipantRepository.java index 9fcebb2..a922080 100644 --- a/src/main/java/com/retrip/trip/application/out/repository/ParticipantRepository.java +++ b/src/main/java/com/retrip/trip/application/out/repository/ParticipantRepository.java @@ -8,4 +8,6 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; -public interface ParticipantRepository extends JpaRepository {} +public interface ParticipantRepository extends JpaRepository { + List findByTripId(UUID tripId); +} diff --git a/src/main/java/com/retrip/trip/application/out/repository/TripRepository.java b/src/main/java/com/retrip/trip/application/out/repository/TripRepository.java index b7a898a..e454283 100644 --- a/src/main/java/com/retrip/trip/application/out/repository/TripRepository.java +++ b/src/main/java/com/retrip/trip/application/out/repository/TripRepository.java @@ -7,7 +7,4 @@ import java.util.Optional; import java.util.UUID; -public interface TripRepository extends JpaRepository { - @Query("select t FROM Trip t JOIN FETCH t.tripParticipants WHERE t.id = :id") - Optional findWithParticipantsById(UUID id); -} +public interface TripRepository extends JpaRepository {} diff --git a/src/main/java/com/retrip/trip/domain/entity/Trip.java b/src/main/java/com/retrip/trip/domain/entity/Trip.java index e52a7e3..6900dcf 100644 --- a/src/main/java/com/retrip/trip/domain/entity/Trip.java +++ b/src/main/java/com/retrip/trip/domain/entity/Trip.java @@ -1,9 +1,6 @@ package com.retrip.trip.domain.entity; -import com.retrip.trip.domain.entity.participant.Participant; -import com.retrip.trip.domain.entity.participant.Participants; import com.retrip.trip.domain.exception.*; -import com.retrip.trip.domain.exception.common.BusinessException; import com.retrip.trip.domain.exception.common.ErrorCode; import com.retrip.trip.domain.exception.common.InvalidValueException; import com.retrip.trip.domain.vo.*; @@ -18,7 +15,6 @@ import java.util.Objects; import java.util.UUID; -import static com.retrip.trip.domain.exception.common.ErrorCode.TRIP_MEMBER_BANNED_CANNOT_APPLY; import static lombok.AccessLevel.PROTECTED; @Getter @@ -49,8 +45,6 @@ public class Trip extends BaseEntity { @Column(name = "category", length = 50, nullable = false) private TripCategory category; - @Embedded private Participants participants; - @Embedded private TripDemands tripDemands; @Embedded private TripPeriod period; @@ -108,16 +102,9 @@ public static Trip createWithItineraries( } public void addDemand(TripDemand demand) { - validateAddDemand(demand); this.tripDemands.addDemand(demand); } - private void validateAddDemand(TripDemand demand) { - if (this.participants.isBan(demand.getMemberId())) { - throw new BusinessException(TRIP_MEMBER_BANNED_CANNOT_APPLY); - } - } - private static void validateMaxParticipants(int maxParticipants) { if (maxParticipants < 1) { throw new InvalidValueException( @@ -141,34 +128,25 @@ public List getItinerariesIds() { return getItineraries().ids(); } - public void banMembers(UUID loginMemberId, List memberIds) { - this.participants.banMembers(loginMemberId, memberIds, this); - } - - public void leave(UUID memberId) { + public void canLeave() { if (!this.status.canLeave()) { throw new TripNotReadyException(); } - - Participant participant = - participants - .findParticipantById(memberId) - .orElseThrow(() -> new NotParticipantException("현재 여행에 참여하고 있지 않습니다.")); - - if (participant.isLeader()) { - throw new LeaderCannotLeaveException(); - } - participants.removeParticipant(memberId); } - public void delegateLeader(UUID currentLeaderId, UUID newLeaderId) { + public void canDelegateLeader() { if (this.status != TripStatus.BEFORE_TRIP) { throw new TripNotReadyException(); } - participants.delegateLeader(currentLeaderId, newLeaderId); } public void updateMaxParticipants(int maxParticipants) { - // todo: 최대 참여자 수 변경 함수 이동 + this.maxParticipants = maxParticipants; + } + + public void canBan() { + if (!TripStatus.RECRUITING.equals(status)) { + throw new IllegalStateException("해당 여행은 모집 중이 아닙니다."); + } } } diff --git a/src/main/java/com/retrip/trip/domain/entity/TripDemand.java b/src/main/java/com/retrip/trip/domain/entity/TripDemand.java index 56e5ae0..d438f37 100644 --- a/src/main/java/com/retrip/trip/domain/entity/TripDemand.java +++ b/src/main/java/com/retrip/trip/domain/entity/TripDemand.java @@ -56,12 +56,6 @@ public void reject() { this.status = TripDemandStatus.REJECTED; } - private void validateTripLeader(UUID memberId) { - if (!this.trip.getParticipants().isLeader(memberId)) { - throw new BusinessException(NOT_TRIP_LEADER); - } - } - public void ensurePending() { if (!TripDemandStatus.PENDING.equals(this.status)) { throw new IllegalStateException("참여 요청의 상태가 '대기' 상태가 아닙니다."); diff --git a/src/main/java/com/retrip/trip/domain/entity/participant/Participant.java b/src/main/java/com/retrip/trip/domain/entity/participant/Participant.java index e12f33d..b359ca4 100644 --- a/src/main/java/com/retrip/trip/domain/entity/participant/Participant.java +++ b/src/main/java/com/retrip/trip/domain/entity/participant/Participant.java @@ -47,20 +47,6 @@ public Participant(UUID tripId, UUID memberId, ParticipantRole role) { this.status = ParticipantStatus.ACTIVE; } - public static Participant createTripParticipant(UUID memberId, Trip trip) { - return new Participant( - UUID.randomUUID(), - null, - memberId, - ParticipantRole.PARTICIPANT, - ParticipantStatus.ACTIVE, - trip); - } - - public boolean isLeader() { - return this.role.isLeader(); - } - public void ban() { this.status = ParticipantStatus.EXPELLED; } diff --git a/src/main/java/com/retrip/trip/domain/entity/participant/Participants.java b/src/main/java/com/retrip/trip/domain/entity/participant/Participants.java deleted file mode 100644 index 58eaaee..0000000 --- a/src/main/java/com/retrip/trip/domain/entity/participant/Participants.java +++ /dev/null @@ -1,128 +0,0 @@ -package com.retrip.trip.domain.entity.participant; - -import com.retrip.trip.domain.entity.Trip; -import com.retrip.trip.domain.exception.NotLeaderException; -import com.retrip.trip.domain.exception.NotParticipantException; -import com.retrip.trip.domain.exception.common.BusinessException; -import com.retrip.trip.domain.exception.common.ErrorCode; -import com.retrip.trip.domain.exception.common.InvalidValueException; -import com.retrip.trip.domain.vo.ParticipantRole; -import com.retrip.trip.domain.vo.ParticipantStatus; -import com.retrip.trip.domain.vo.TripStatus; -import jakarta.persistence.Embeddable; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -import static com.retrip.trip.domain.exception.common.ErrorCode.NOT_TRIP_LEADER; -import static com.retrip.trip.domain.exception.common.ErrorCode.TRIP_MEMBER_NOT_IN_TRIP; -import static lombok.AccessLevel.PROTECTED; - -@Getter -@Embeddable -@NoArgsConstructor(access = PROTECTED, force = true) -public class Participants { - - public boolean updatableByLeader(UUID memberId) { - return isLeader(memberId); - } - - public boolean isLeader(UUID memberId) { - return this.values.stream() - .filter(m -> memberId.equals(m.getMemberId())) - .findFirst() - .orElseThrow( - () -> new InvalidValueException(ErrorCode.LEADER_REQUIRED, "여행 회원이 아닙니다.")) - .isLeader(); - } - - public void banMembers(UUID loginMemberId, List memberIds, Trip trip) { - validateTripRecruitingStatus(trip.getStatus()); - validateTripLeader(loginMemberId); - validateExistParticipantMember(memberIds); - - List participantsToBan = - values.stream().filter(m -> memberIds.contains(m.getMemberId())).toList(); - - participantsToBan.forEach(Participant::ban); - } - - private void validateExistParticipantMember(List memberIds) { - boolean isAllExist = values.stream().anyMatch(m -> memberIds.contains(m.getMemberId())); - - if (!isAllExist) { - throw new BusinessException(TRIP_MEMBER_NOT_IN_TRIP); - } - } - - private void validateTripLeader(UUID loginMemberId) { - if (!isLeader(loginMemberId)) { - throw new BusinessException(NOT_TRIP_LEADER); - } - } - - public void validateTripRecruitingStatus(TripStatus status) { - if (!TripStatus.RECRUITING.equals(status)) { - throw new IllegalStateException("해당 여행은 모집 중이 아닙니다."); - } - } - - public boolean isBan(UUID memberId) { - return values.stream() - .anyMatch( - tripParticipant -> - memberId.equals(tripParticipant.getMemberId()) - && tripParticipant.getStatus() - == ParticipantStatus.EXPELLED); - } - - public boolean anyDuplicate(List memberIds) { - return values.stream().anyMatch(p -> memberIds.contains(p.getMemberId())); - } - - public void removeParticipant(UUID memberId) { - this.values.removeIf(p -> p.getMemberId().equals(memberId)); - } - - public Optional findParticipantById(UUID memberId) { - return this.values.stream().filter(p -> p.getMemberId().equals(memberId)).findFirst(); - } - - public void delegateLeader(UUID currentLeaderId, UUID newLeaderId) { - validateLeaderDelegation(currentLeaderId, newLeaderId); - - Participant oldLeader = - findParticipantById(currentLeaderId) - .orElseThrow(() -> new NotParticipantException("현재 리더를 찾을 수 없습니다.")); - Participant newLeader = - findParticipantById(newLeaderId) - .orElseThrow( - () -> - new NotParticipantException( - "새로운 리더가 될 멤버가 여행에 참여하고 있지 않습니다.")); - - changeLeader(oldLeader, newLeader); - } - - private void validateLeaderDelegation(UUID currentLeaderId, UUID newLeaderId) { - if (currentLeaderId.equals(newLeaderId)) { - throw new InvalidValueException("자기 자신에게 리더를 위임할 수 없습니다."); - } - if (!isLeader(currentLeaderId)) { - throw new NotLeaderException(); - } - } - - private void changeLeader(Participant oldLeader, Participant newLeader) { - oldLeader.changeRole(ParticipantRole.PARTICIPANT); - newLeader.changeRole(ParticipantRole.LEADER); - } - - public boolean isFull() { - // return this.values.size() >= this.maxParticipants; - return this.values.size() >= 0; - } -} diff --git a/src/main/java/com/retrip/trip/domain/exception/LeaderCannotLeaveException.java b/src/main/java/com/retrip/trip/domain/exception/LeaderCannotLeaveException.java deleted file mode 100644 index ba471d7..0000000 --- a/src/main/java/com/retrip/trip/domain/exception/LeaderCannotLeaveException.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.retrip.trip.domain.exception; - -import com.retrip.trip.domain.exception.common.BusinessException; -import com.retrip.trip.domain.exception.common.ErrorCode; - -public class LeaderCannotLeaveException extends BusinessException { - private static final ErrorCode errorCode = ErrorCode.LEADER_CANNOT_LEAVE; - - public LeaderCannotLeaveException() { - super(errorCode); - } -} \ No newline at end of file diff --git a/src/main/java/com/retrip/trip/domain/service/InvitationPolicy.java b/src/main/java/com/retrip/trip/domain/service/InvitationPolicy.java index 0697d73..f88ee48 100644 --- a/src/main/java/com/retrip/trip/domain/service/InvitationPolicy.java +++ b/src/main/java/com/retrip/trip/domain/service/InvitationPolicy.java @@ -1,42 +1,19 @@ package com.retrip.trip.domain.service; import com.retrip.trip.domain.entity.Trip; -import com.retrip.trip.domain.entity.participant.Participants; import com.retrip.trip.domain.entity.invitation.Invitation; import com.retrip.trip.domain.exception.*; import com.retrip.trip.domain.exception.common.IllegalStateException; import org.springframework.stereotype.Service; -import java.util.List; -import java.util.UUID; - import static com.retrip.trip.domain.vo.TripStatus.RECRUITING; @Service public class InvitationPolicy { - public void canInvite(Trip trip, UUID leaderId, List memberIds) { - Participants participants = trip.getParticipants(); + public void canInvite(Trip trip) { if (trip.getStatus().cannotCreateInvitations()) { throw new IllegalStateException("여행 초대를 생성할 수 없는 상태입니다. " + trip.getStatus().name()); } - - if (isNotLeader(trip.getParticipants(), leaderId)) { - throw new NotLeaderException(); - } - - if (participants.anyDuplicate(memberIds)) { - throw new TripInvitationDuplicateException("여행 멤버로 등록된 사용자는 초대할 수 없습니다."); - } - } - - public void canViewInvitations(Trip trip, UUID leaderId) { - if (isNotLeader(trip.getParticipants(), leaderId)) { - throw new NotLeaderException(); - } - } - - public boolean isNotLeader(Participants participants, UUID leaderId) { - return !participants.isLeader(leaderId); } public void canAccept(Trip trip, Invitation invitation) { @@ -46,10 +23,6 @@ public void canAccept(Trip trip, Invitation invitation) { if (trip.getStatus() != RECRUITING) { throw new TripNotRecruitingException(); } - Participants participants = trip.getParticipants(); - if (participants.isFull()) { - throw new TripParticipantsIsFullException(); - } } public void canReject(Invitation invitation) { diff --git a/src/main/java/com/retrip/trip/domain/service/ParticipantPolicy.java b/src/main/java/com/retrip/trip/domain/service/ParticipantPolicy.java index 4e8d9e9..2b7a560 100644 --- a/src/main/java/com/retrip/trip/domain/service/ParticipantPolicy.java +++ b/src/main/java/com/retrip/trip/domain/service/ParticipantPolicy.java @@ -1,8 +1,22 @@ package com.retrip.trip.domain.service; +import static com.retrip.trip.domain.exception.common.ErrorCode.TRIP_MEMBER_BANNED_CANNOT_APPLY; +import static com.retrip.trip.domain.exception.common.ErrorCode.TRIP_MEMBER_NOT_IN_TRIP; + +import com.retrip.trip.domain.entity.participant.Participant; import com.retrip.trip.domain.exception.NotLeaderException; +import com.retrip.trip.domain.exception.NotParticipantException; import com.retrip.trip.domain.exception.ParticipantFullException; +import com.retrip.trip.domain.exception.TripInvitationDuplicateException; +import com.retrip.trip.domain.exception.common.BusinessException; +import com.retrip.trip.domain.exception.common.InvalidValueException; import com.retrip.trip.domain.vo.ParticipantRole; + +import java.util.List; +import java.util.Optional; + +import java.util.UUID; + import org.springframework.stereotype.Service; @Service @@ -19,4 +33,42 @@ public void validateLeader(ParticipantRole role) { throw new NotLeaderException(); } } + + public void validateParticipant(ParticipantRole role) { + if (!role.isParticipant()) { + throw new NotParticipantException("회원은 참여자가 아닙니다."); + } + } + + public void validateDelegate(Participant currentLeader, Participant newLeader) { + if (currentLeader.equals(newLeader)) { + throw new InvalidValueException("자기 자신에게 리더를 위임할 수 없습니다."); + } + if (!currentLeader.getRole().isLeader()) { + throw new NotLeaderException(); + } + } + + public void changeLeader(Participant oldLeader, Participant newLeader) { + oldLeader.changeRole(ParticipantRole.PARTICIPANT); + newLeader.changeRole(ParticipantRole.LEADER); + } + + public void validateInvite(List participants, List memberIds) { + if (participants.stream().anyMatch(p -> memberIds.contains(p.getMemberId()))) { + throw new TripInvitationDuplicateException("여행 멤버로 등록된 사용자는 초대할 수 없습니다."); + } + } + + public void validateBan(List participants, List memberIds) { + if (participants.stream().noneMatch(p -> memberIds.contains(p.getMemberId()))) { + throw new BusinessException(TRIP_MEMBER_NOT_IN_TRIP); + } + } + + public void validateDemand(Participant participant) { + if (participant.getStatus().isExpelled()) { + throw new BusinessException(TRIP_MEMBER_BANNED_CANNOT_APPLY); + } + } } diff --git a/src/main/java/com/retrip/trip/domain/vo/ParticipantRole.java b/src/main/java/com/retrip/trip/domain/vo/ParticipantRole.java index de2ba7f..3ca0d74 100644 --- a/src/main/java/com/retrip/trip/domain/vo/ParticipantRole.java +++ b/src/main/java/com/retrip/trip/domain/vo/ParticipantRole.java @@ -8,7 +8,6 @@ @Getter @AllArgsConstructor public enum ParticipantRole { - LEADER("LEADER", "리더"), PARTICIPANT("PARTICIPANT", "참가자"); private final String code; @@ -24,4 +23,8 @@ public static ParticipantRole codeOf(String code) { public boolean isLeader() { return this == LEADER; } + + public boolean isParticipant() { + return this == PARTICIPANT; + } } diff --git a/src/main/java/com/retrip/trip/domain/vo/ParticipantStatus.java b/src/main/java/com/retrip/trip/domain/vo/ParticipantStatus.java index da63432..ccd413e 100644 --- a/src/main/java/com/retrip/trip/domain/vo/ParticipantStatus.java +++ b/src/main/java/com/retrip/trip/domain/vo/ParticipantStatus.java @@ -7,6 +7,10 @@ @Getter @AllArgsConstructor public enum ParticipantStatus { + ACTIVE, + EXPELLED; - ACTIVE, EXPELLED; + public boolean isExpelled() { + return this == EXPELLED; + } } diff --git a/src/main/java/com/retrip/trip/infra/adapter/in/presentation/rest/TripController.java b/src/main/java/com/retrip/trip/infra/adapter/in/presentation/rest/TripController.java index fce9a0a..a8ba388 100644 --- a/src/main/java/com/retrip/trip/infra/adapter/in/presentation/rest/TripController.java +++ b/src/main/java/com/retrip/trip/infra/adapter/in/presentation/rest/TripController.java @@ -29,6 +29,7 @@ public class TripController { private final TripPeriodUseCase tripPeriodUseCase; private final LeaveTripUseCase leaveTripUseCase; private final DelegateLeaderUseCase delegateLeaderUseCase; + private final TripManagementUseCase tripManagementUseCase; @GetMapping("/categories") @Schema(description = "여행 카테고리 목록 조회") @@ -134,4 +135,13 @@ public ApiResponse banMembers( tripDemandUseCase.banMembers(memberId, tripId, request.memberIds()); return ApiResponse.noContent(); } + + @PutMapping("/{tripId}/max-participants") + @Schema(description = "여행 최대 참여수 수정") + public ResponseEntity updateMaxParticipants( + @PathVariable UUID tripId, @RequestBody MaxParticipantUpdateRequest request) { + MaxParticipantUpdateResponse response = + tripManagementUseCase.updateMaxParticipants(tripId, request); + return ResponseEntity.ok().body(response); + } } diff --git a/src/test/java/com/retrip/trip/application/in/ParticipantServiceTest.java b/src/test/java/com/retrip/trip/application/in/ParticipantServiceTest.java index 4e0ecb6..f475a66 100644 --- a/src/test/java/com/retrip/trip/application/in/ParticipantServiceTest.java +++ b/src/test/java/com/retrip/trip/application/in/ParticipantServiceTest.java @@ -3,13 +3,19 @@ import static com.retrip.trip.domain.fixture.TripFixture.*; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; import com.retrip.trip.application.in.base.BaseInvitationServiceTest; import com.retrip.trip.application.in.service.ParticipantService; +import com.retrip.trip.domain.entity.Trip; import com.retrip.trip.domain.entity.participant.Participant; +import com.retrip.trip.domain.exception.NotLeaderException; +import com.retrip.trip.domain.fixture.ParticipantFixture; import com.retrip.trip.domain.vo.ParticipantRole; import com.retrip.trip.domain.vo.ParticipantStatus; + +import jakarta.transaction.Transactional; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; @@ -32,13 +38,24 @@ class ParticipantServiceTest extends BaseInvitationServiceTest { assertThat(response.getStatus()).isEqualTo(ParticipantStatus.ACTIVE); } + @Test + void 참여자를_제거한다() { + // given + Participant response = participantService.createParticipant(TRIP_ID, MEMBER_ID, 4); + + // when + + // then + assertDoesNotThrow(() -> participantService.remove(TRIP_ID, MEMBER_ID)); + } + @Test void 내가_참여중인_여행을_볼_수_있다() { // given - participantService.createLeaderParticipant(TRIP_ID, MEMBER_ID); - participantService.createLeaderParticipant(TRIP_ID, MEMBER_ID); - participantService.createLeaderParticipant(TRIP_ID, MEMBER_ID); - participantService.createLeaderParticipant(TRIP_ID, MEMBER_ID); + participantService.createParticipant(TRIP_ID, MEMBER_ID, 10); + participantService.createParticipant(TRIP_ID, MEMBER_ID, 10); + participantService.createParticipant(TRIP_ID, MEMBER_ID, 10); + participantService.createParticipant(TRIP_ID, MEMBER_ID, 10); // when List response = @@ -50,11 +67,10 @@ class ParticipantServiceTest extends BaseInvitationServiceTest { @Test void 내가_참여중인_여행을_총_갯수를_볼_수_있다() { - // given - participantService.createLeaderParticipant(TRIP_ID, MEMBER_ID); - participantService.createLeaderParticipant(TRIP_ID, MEMBER_ID); - participantService.createLeaderParticipant(TRIP_ID, MEMBER_ID); - participantService.createLeaderParticipant(TRIP_ID, MEMBER_ID); + participantService.createParticipant(TRIP_ID, MEMBER_ID, 10); + participantService.createParticipant(TRIP_ID, MEMBER_ID, 10); + participantService.createParticipant(TRIP_ID, MEMBER_ID, 10); + participantService.createParticipant(TRIP_ID, MEMBER_ID, 10); // when Long response = participantService.findByMemberIdTotalCount(MEMBER_ID); @@ -62,4 +78,95 @@ class ParticipantServiceTest extends BaseInvitationServiceTest { // then assertThat(response).isEqualTo(4); } + + @Test + public void 리더는_정상_동작() { + // given + Trip trip = createTrip(TRIP_ID); + Participant leader = ParticipantFixture.createLeaderParticipant(TRIP_ID, LEADER_ID); + + // when + // then + assertDoesNotThrow( + () -> + participantService.requireLeaderOrElseThrow( + trip.getId(), leader.getMemberId())); + } + + @Test + public void 리더가_아니면_오류_발생() { + // given + Trip trip = createTrip(TRIP_ID); + Participant participant = ParticipantFixture.createParticipant(TRIP_ID, MEMBER_ID); + + // when + // then + assertThrows( + NotLeaderException.class, + () -> + participantService.requireLeaderOrElseThrow( + trip.getId(), participant.getMemberId())); + } + + @Test + public void 참여자는_정상_동작() { + // given + Trip trip = createTrip(TRIP_ID); + Participant participant = ParticipantFixture.createParticipant(TRIP_ID, LEADER_ID); + + // when + // then + assertDoesNotThrow( + () -> + participantService.requireParticipantOrElseThrow( + trip.getId(), participant.getMemberId())); + } + + @Test + public void 참여자가_아니면_오류_발생() { + // given + Trip trip = createTrip(TRIP_ID); + Participant leader = ParticipantFixture.createLeaderParticipant(TRIP_ID, MEMBER_ID); + + // when + // then + assertThrows( + NotLeaderException.class, + () -> + participantService.requireParticipantOrElseThrow( + trip.getId(), leader.getMemberId())); + } + + @Test + public void 리더를_위임_한다() { + // given + Trip trip = createTrip(TRIP_ID); + Participant leader = ParticipantFixture.createLeaderParticipant(TRIP_ID, LEADER_ID); + Participant participant = ParticipantFixture.createParticipant(TRIP_ID, LEADER_ID); + + // when + Participant response = + participantService.delegateLeader( + TRIP_ID, leader.getMemberId(), participant.getMemberId()); + + // then + assertThat(response.getRole()).isEqualTo(ParticipantRole.LEADER); + assertThat(leader.getRole()).isEqualTo(ParticipantRole.PARTICIPANT); + } + + @Test + public void 방장은_여행에_참여하는_인원을_수락할_수_있다() { + // given + Trip trip = createTrip(TRIP_ID); + Participant leader = ParticipantFixture.createLeaderParticipant(TRIP_ID, LEADER_ID); + + // when + ; + + // then + assertDoesNotThrow( + () -> + participantService.canInvite( + TRIP_ID, leader.getMemberId(), List.of(홍석_ID, 정수_ID))); + } } diff --git a/src/test/java/com/retrip/trip/application/in/TripServiceTest.java b/src/test/java/com/retrip/trip/application/in/TripServiceTest.java index 014f287..e9dea3c 100644 --- a/src/test/java/com/retrip/trip/application/in/TripServiceTest.java +++ b/src/test/java/com/retrip/trip/application/in/TripServiceTest.java @@ -1,16 +1,23 @@ package com.retrip.trip.application.in; +import static com.retrip.trip.domain.fixture.TripFixture.MEMBER_ID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + import com.retrip.trip.application.in.base.BaseTripServiceTest; import com.retrip.trip.application.in.request.*; import com.retrip.trip.application.in.response.*; -import com.retrip.trip.domain.entity.participant.Participant; import com.retrip.trip.domain.entity.Trip; import com.retrip.trip.domain.entity.TripDemand; +import com.retrip.trip.domain.entity.participant.Participant; import com.retrip.trip.domain.exception.*; import com.retrip.trip.domain.exception.common.BusinessException; import com.retrip.trip.domain.exception.common.InvalidValueException; +import com.retrip.trip.domain.fixture.ParticipantFixture; import com.retrip.trip.domain.fixture.TripFixture; import com.retrip.trip.domain.vo.*; + import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.data.domain.Page; @@ -21,15 +28,21 @@ import java.util.List; import java.util.UUID; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - class TripServiceTest extends BaseTripServiceTest { private TripPeriod createFuturePeriod() { return new TripPeriod(LocalDate.now().plusDays(1), LocalDate.now().plusDays(5)); } + private Participant createLeader(UUID tripId, UUID memberId) { + return participantRepository.save( + ParticipantFixture.createLeaderParticipant(tripId, memberId)); + } + + private Participant createParticipant(UUID tripId, UUID newMemberId) { + return participantRepository.save( + ParticipantFixture.createParticipant(tripId, newMemberId)); + } + private Trip createTestTrip(String title, String description, TripCategory category) { TripPeriod period = createFuturePeriod(); Trip trip = @@ -44,7 +57,7 @@ private Trip createTestTrip(String title, String description, TripCategory categ return tripRepository.save(trip); } - private Trip createReadyTrip(UUID leaderId) { + private Trip createReadyTrip() { Trip trip = Trip.create( locationId, @@ -140,6 +153,8 @@ private Trip createProgressTrip(UUID leaderId) { void 사용자가_참여_요청을_보낸다() { // given Trip trip = createTestTrip("테스트 여행", "여행 설명", TripCategory.DOMESTIC); + Participant leader = ParticipantFixture.createLeaderParticipant(trip.getId(), MEMBER_ID); + TripDemandRequest request = new TripDemandRequest(newMemberId, "참여 요청 메시지"); // when @@ -155,8 +170,9 @@ private Trip createProgressTrip(UUID leaderId) { @Test void 리더가_참여_요청을_승인하면_실제_참여자로_등록된다() { // given - Trip newTrip = - TripFixture.createTestTrip(memberId, "승인 테스트 여행", "여행 설명", TripCategory.DOMESTIC); + Trip newTrip = TripFixture.createTestTrip("승인 테스트 여행", "여행 설명", TripCategory.DOMESTIC); + Participant leader = ParticipantFixture.createLeaderParticipant(newTrip.getId(), MEMBER_ID); + TripDemand tripDemand = TripDemand.create(newMemberId, newTrip, "참여 요청 메시지"); newTrip.getTripDemands().getValues().add(tripDemand); tripRepository.save(newTrip); @@ -164,25 +180,23 @@ private Trip createProgressTrip(UUID leaderId) { // then TripDemandApproveResponse response = tripService.approve(memberId, newTrip.getId(), tripDemand.getId()); - Trip trip = tripRepository.findById(newTrip.getId()).orElseThrow(); - UUID newParticipantMemberId = - trip.getParticipants().getValues().stream() - .map(Participant::getMemberId) - .filter(id -> id.equals(newMemberId)) - .findFirst() + Participant newParticipantMember = + participantQueryRepository + .findByTripIdAndMemberId(newTrip.getId(), newMemberId) .orElseThrow(); // when assertThat(response).isNotNull(); - assertThat(newParticipantMemberId).isEqualTo(newMemberId); + assertThat(newParticipantMember.getMemberId()).isEqualTo(newMemberId); assertThat(response.statusCode()).isEqualTo(TripDemandStatus.APPROVED.getCode()); } @Test void 리더가_아니면_참여_요청을_승인할_수_없다() { // given - Trip newTrip = - TripFixture.createTestTrip(memberId, "승인 테스트 여행", "여행 설명", TripCategory.DOMESTIC); + Trip newTrip = TripFixture.createTestTrip("승인 테스트 여행", "여행 설명", TripCategory.DOMESTIC); + Participant leader = ParticipantFixture.createLeaderParticipant(newTrip.getId(), MEMBER_ID); + TripDemand tripDemand = TripDemand.create(newMemberId, newTrip, "참여 요청 메시지"); newTrip.getTripDemands().getValues().add(tripDemand); tripRepository.save(newTrip); @@ -197,8 +211,9 @@ private Trip createProgressTrip(UUID leaderId) { @Test void 리더가_참여_요청을_거절하면_요청_상태가_거절로_변경된다() { // given - Trip newTrip = - TripFixture.createTestTrip(memberId, "거절 테스트 여행", "여행 설명", TripCategory.DOMESTIC); + Trip newTrip = TripFixture.createTestTrip("거절 테스트 여행", "여행 설명", TripCategory.DOMESTIC); + Participant leader = ParticipantFixture.createLeaderParticipant(newTrip.getId(), MEMBER_ID); + TripDemand tripDemand = TripDemand.create(newMemberId, newTrip, "참여 요청 메시지"); newTrip.getTripDemands().getValues().add(tripDemand); tripRepository.save(newTrip); @@ -215,8 +230,8 @@ private Trip createProgressTrip(UUID leaderId) { @Test void 리더가_아니면_참여_요청을_거절할_수_없다() { // given - Trip newTrip = - TripFixture.createTestTrip(memberId, "거절 테스트 여행", "여행 설명", TripCategory.DOMESTIC); + Trip newTrip = TripFixture.createTestTrip("거절 테스트 여행", "여행 설명", TripCategory.DOMESTIC); + Participant leader = ParticipantFixture.createLeaderParticipant(newTrip.getId(), MEMBER_ID); TripDemand tripDemand = TripDemand.create(newMemberId, newTrip, "참여 요청 메시지"); newTrip.getTripDemands().getValues().add(tripDemand); tripRepository.save(newTrip); @@ -499,31 +514,36 @@ void updatePeriodIsAfterPrePeriodEndAfter() { @Test @DisplayName("멤버가 성공적으로 여행을 나간다") - void leaveTrip_success_forMember() { + void canLeaveTrip_success_forMember() { // given - Trip trip = createReadyTrip(memberId); - trip.addParticipant(Participant.createTripParticipant(newMemberId, trip)); - tripRepository.save(trip); + Trip trip = createReadyTrip(); + Participant leader = ParticipantFixture.createLeaderParticipant(trip.getId(), memberId); + Participant participant = ParticipantFixture.createParticipant(trip.getId(), newMemberId); + participantRepository.save(leader); + participantRepository.save(participant); // when tripService.leaveTrip(trip.getId(), newMemberId); // then - Trip updatedTrip = tripRepository.findById(trip.getId()).get(); boolean isParticipantPresent = - updatedTrip.getParticipants().findParticipantById(newMemberId).isPresent(); + participantQueryRepository + .findByTripIdAndMemberId(trip.getId(), newMemberId) + .isPresent(); assertThat(isParticipantPresent).isFalse(); } @Test @DisplayName("리더는 위임 없이 여행을 나갈 수 없다") - void leaveTrip_fail_forLeader() { + void canLeaveTrip_fail_forLeader() { // given - Trip trip = createReadyTrip(memberId); + Trip trip = createReadyTrip(); + Participant leader = createLeader(trip.getId(), memberId); + Participant participant = createParticipant(trip.getId(), newMemberId); // when & then assertThrows( - LeaderCannotLeaveException.class, + NotParticipantException.class, () -> { tripService.leaveTrip(trip.getId(), memberId); }); @@ -531,11 +551,11 @@ void leaveTrip_fail_forLeader() { @Test @DisplayName("여행이 '여행 전' 상태가 아니면 나갈 수 없다") - void leaveTrip_fail_whenTripNotReady() { + void canLeaveTrip_fail_whenTripNotReady() { // given Trip trip = createProgressTrip(memberId); - trip.addParticipant(Participant.createTripParticipant(newMemberId, trip)); - tripRepository.save(trip); + Participant leader = createLeader(trip.getId(), memberId); + Participant participant = createParticipant(trip.getId(), newMemberId); // when & then assertThrows( @@ -547,30 +567,29 @@ void leaveTrip_fail_whenTripNotReady() { @Test @DisplayName("리더가 성공적으로 다른 멤버에게 리더를 위임한다") - void delegateLeader_success() { + void canDelegateLeader_success() { // given - Trip trip = createReadyTrip(memberId); - trip.addParticipant(Participant.createTripParticipant(newMemberId, trip)); - tripRepository.save(trip); + Trip trip = createReadyTrip(); + Participant leader = createLeader(trip.getId(), memberId); + Participant participant = createParticipant(trip.getId(), newMemberId); + DelegateLeaderRequest request = new DelegateLeaderRequest(memberId, newMemberId); // when - tripService.delegateLeader(trip.getId(), request); + DelegateLeaderResponse response = tripService.delegateLeader(trip.getId(), request); // then - Trip updatedTrip = tripRepository.findById(trip.getId()).get(); - assertTrue(updatedTrip.getParticipants().findParticipantById(newMemberId).get().isLeader()); - assertThat(updatedTrip.getParticipants().findParticipantById(memberId).get().isLeader()) - .isFalse(); + assertThat(response.role()).isEqualTo(ParticipantRole.LEADER.getViewName()); } @Test @DisplayName("리더가 아닌 멤버는 리더를 위임할 수 없다") - void delegateLeader_fail_notLeader() { + void canDelegateLeader_fail_notLeader() { // given - Trip trip = createReadyTrip(memberId); - trip.addParticipant(Participant.createTripParticipant(newMemberId, trip)); - tripRepository.save(trip); + Trip trip = createReadyTrip(); + Participant leader = createLeader(trip.getId(), memberId); + Participant participant = createParticipant(trip.getId(), newMemberId); + DelegateLeaderRequest request = new DelegateLeaderRequest(newMemberId, memberId); // when & then @@ -583,9 +602,12 @@ void delegateLeader_fail_notLeader() { @Test @DisplayName("리더는 자기 자신에게 리더를 위임할 수 없다") - void delegateLeader_fail_toSelf() { + void canDelegateLeader_fail_toSelf() { // given - Trip trip = createReadyTrip(memberId); + Trip trip = createReadyTrip(); + Participant leader = createLeader(trip.getId(), memberId); + Participant participant = createParticipant(trip.getId(), newMemberId); + DelegateLeaderRequest request = new DelegateLeaderRequest(memberId, memberId); // when & then @@ -598,11 +620,14 @@ void delegateLeader_fail_toSelf() { @Test @DisplayName("참여자가 아닌 사람에게 리더를 위임할 수 없다") - void delegateLeader_fail_toNonParticipant() { + void canDelegateLeader_fail_toNonParticipant() { // given - Trip trip = createReadyTrip(memberId); - UUID nonParticipantId = UUID.randomUUID(); - DelegateLeaderRequest request = new DelegateLeaderRequest(memberId, nonParticipantId); + Trip trip = createReadyTrip(); + Participant leader = createLeader(trip.getId(), memberId); + Participant participant = createParticipant(trip.getId(), newMemberId); + + UUID unknownMember = UUID.randomUUID(); + DelegateLeaderRequest request = new DelegateLeaderRequest(memberId, unknownMember); // when & then assertThrows( @@ -616,9 +641,9 @@ void delegateLeader_fail_toNonParticipant() { @DisplayName("여행을 나간 후 '나의 여행 목록'에 보이지 않는다") void getMyTrips_afterLeaving() { // given - Trip trip = createReadyTrip(memberId); - trip.addParticipant(Participant.createTripParticipant(newMemberId, trip)); - tripRepository.save(trip); + Trip trip = createReadyTrip(); + Participant leader = createLeader(trip.getId(), memberId); + Participant participant = createParticipant(trip.getId(), newMemberId); // when tripService.leaveTrip(trip.getId(), newMemberId); @@ -631,8 +656,9 @@ void getMyTrips_afterLeaving() { @Test void 리더는_참여자들을_추방할_수_있다() { // given - Trip newTrip = - TripFixture.createTestTrip(memberId, "승인 테스트 여행", "여행 설명", TripCategory.DOMESTIC); + Trip newTrip = TripFixture.createTestTrip("승인 테스트 여행", "여행 설명", TripCategory.DOMESTIC); + Participant leader = createLeader(newTrip.getId(), memberId); + TripDemand tripDemand = TripDemand.create(newMemberId, newTrip, "참여 요청 메시지"); newTrip.getTripDemands().getValues().add(tripDemand); tripRepository.save(newTrip); @@ -644,9 +670,8 @@ void getMyTrips_afterLeaving() { Trip trip = tripRepository.findById(newTrip.getId()).orElseThrow(); Participant banParticipant = - trip.getParticipants().getValues().stream() - .filter(participant -> participant.getMemberId().equals(newMemberId)) - .findFirst() + participantQueryRepository + .findByTripIdAndMemberId(trip.getId(), newMemberId) .orElseThrow(); // when @@ -657,8 +682,8 @@ void getMyTrips_afterLeaving() { @Test void 해당_여행에_강퇴당한_사용자는_다시_참여요청할_수_없다() { // given - Trip newTrip = - TripFixture.createTestTrip(memberId, "승인 테스트 여행", "여행 설명", TripCategory.DOMESTIC); + Trip newTrip = TripFixture.createTestTrip("승인 테스트 여행", "여행 설명", TripCategory.DOMESTIC); + Participant leader = createLeader(newTrip.getId(), memberId); TripDemand tripDemand = TripDemand.create(newMemberId, newTrip, "참여 요청 메시지"); newTrip.getTripDemands().getValues().add(tripDemand); tripRepository.save(newTrip); @@ -711,22 +736,21 @@ void leader_can_update_maxParticipants() { 3, TripCategory.DOMESTIC); tripRepository.save(trip); + Participant leader = createLeader(trip.getId(), memberId); // when - int newMaxParticipants = 5; - trip.getParticipants().updateMaxParticipants(newMaxParticipants, memberId); - tripRepository.save(trip); + MaxParticipantUpdateRequest request = new MaxParticipantUpdateRequest(5, memberId); + MaxParticipantUpdateResponse response = + tripService.updateMaxParticipants(trip.getId(), request); // then - Trip updatedTrip = tripRepository.findById(trip.getId()).get(); - assertThat(updatedTrip.getMaxParticipants()).isEqualTo(newMaxParticipants); + assertThat(response.maxParticipants()).isEqualTo(request.maxParticipants()); } @Test @DisplayName("리더가 아닌 멤버는 최대 참여 인원을 변경할 수 없다.") void nonLeader_cannot_update_maxParticipants() { // given - UUID nonLeaderId = UUID.randomUUID(); Trip trip = Trip.create( UUID.randomUUID(), @@ -736,14 +760,19 @@ void nonLeader_cannot_update_maxParticipants() { true, 3, TripCategory.DOMESTIC); - trip.addParticipant(Participant.createTripParticipant(nonLeaderId, trip)); - tripRepository.save(trip); + Participant leader = createLeader(trip.getId(), memberId); - // when & then + UUID unknownMember = UUID.randomUUID(); + Participant participant = createParticipant(trip.getId(), unknownMember); + + // when + MaxParticipantUpdateRequest request = new MaxParticipantUpdateRequest(5, unknownMember); + + // then assertThrows( NotLeaderException.class, () -> { - trip.getParticipants().updateMaxParticipants(5, nonLeaderId); + tripService.updateMaxParticipants(trip.getId(), request); }); } @@ -760,46 +789,16 @@ void cannot_update_maxParticipants_lessThan_currentParticipants() { true, 4, TripCategory.DOMESTIC); - trip.addParticipant(Participant.createTripParticipant(UUID.randomUUID(), trip)); - trip.addParticipant(Participant.createTripParticipant(UUID.randomUUID(), trip)); tripRepository.save(trip); + Participant leader = createLeader(trip.getId(), memberId); + Participant participant = createParticipant(trip.getId(), UUID.randomUUID()); // when & then assertThrows( InvalidValueException.class, () -> { - trip.getParticipants().updateMaxParticipants(2, memberId); - }); - } - - @Test - @DisplayName("isLeader 메서드가 정확하게 리더와 멤버를 구분하는지 확인한다.") - void isLeader_check_works_correctly() { - // given - UUID nonLeaderId = UUID.randomUUID(); - Trip trip = - Trip.create( - UUID.randomUUID(), - new TripTitle("isLeader 테스트"), - new TripDescription("설명"), - new TripPeriod(LocalDate.now().plusDays(1), LocalDate.now().plusDays(5)), - true, - 3, - TripCategory.DOMESTIC); - trip.addParticipant(Participant.createTripParticipant(nonLeaderId, trip)); - tripRepository.save(trip); - - // when - Trip savedTrip = tripRepository.findById(trip.getId()).get(); - boolean isLeaderResult = savedTrip.getParticipants().isLeader(memberId); - boolean isNotLeaderResult = savedTrip.getParticipants().isLeader(nonLeaderId); - - // then - assertTrue(isLeaderResult); - assertThrows( - InvalidValueException.class, - () -> { - savedTrip.getParticipants().isLeader(UUID.randomUUID()); + tripService.updateMaxParticipants( + trip.getId(), new MaxParticipantUpdateRequest(2, memberId)); }); } } diff --git a/src/test/java/com/retrip/trip/application/in/base/BaseInvitationServiceTest.java b/src/test/java/com/retrip/trip/application/in/base/BaseInvitationServiceTest.java index cf48b5a..3fcf96d 100644 --- a/src/test/java/com/retrip/trip/application/in/base/BaseInvitationServiceTest.java +++ b/src/test/java/com/retrip/trip/application/in/base/BaseInvitationServiceTest.java @@ -2,26 +2,40 @@ import com.querydsl.jpa.impl.JPAQueryFactory; import com.retrip.trip.application.in.service.InvitationService; +import com.retrip.trip.application.in.service.ParticipantService; +import com.retrip.trip.application.in.service.TripService; import com.retrip.trip.application.out.repository.InvitationRepository; +import com.retrip.trip.application.out.repository.ParticipantQueryRepository; +import com.retrip.trip.application.out.repository.ParticipantRepository; import com.retrip.trip.application.out.repository.TripRepository; import com.retrip.trip.domain.service.InvitationPolicy; +import com.retrip.trip.domain.service.ParticipantPolicy; +import com.retrip.trip.infra.adapter.out.persistence.mysql.query.ParticipantQuerydslRepository; import org.junit.jupiter.api.BeforeEach; import org.springframework.beans.factory.annotation.Autowired; public abstract class BaseInvitationServiceTest extends BaseServiceTest { - @Autowired - protected TripRepository tripRepository; + @Autowired protected TripRepository tripRepository; - @Autowired - protected InvitationRepository invitationRepository; + @Autowired protected InvitationRepository invitationRepository; protected InvitationPolicy invitationPolicy = new InvitationPolicy(); - @Autowired - protected JPAQueryFactory jpaQueryFactory; + @Autowired protected JPAQueryFactory jpaQueryFactory; protected InvitationService invitationService; + protected ParticipantService participantService; + protected ParticipantPolicy participantPolicy = new ParticipantPolicy(); + @Autowired protected ParticipantRepository participantRepository; + protected ParticipantQueryRepository participantQueryRepository; + @BeforeEach void setUp() { - invitationService = new InvitationService(tripRepository, invitationRepository, invitationPolicy); + participantQueryRepository = new ParticipantQuerydslRepository(jpaQueryFactory); + participantService = + new ParticipantService( + participantPolicy, participantRepository, participantQueryRepository); + invitationService = + new InvitationService( + tripRepository, invitationRepository, participantService, invitationPolicy); } } diff --git a/src/test/java/com/retrip/trip/domain/entity/ParticipantsTest.java b/src/test/java/com/retrip/trip/domain/entity/ParticipantsTest.java deleted file mode 100644 index dcbcf4e..0000000 --- a/src/test/java/com/retrip/trip/domain/entity/ParticipantsTest.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.retrip.trip.domain.entity; - -import com.retrip.trip.domain.entity.participant.Participant; -import com.retrip.trip.domain.entity.participant.Participants; -import com.retrip.trip.domain.exception.ParticipantFullException; -import com.retrip.trip.domain.exception.common.InvalidValueException; -import com.retrip.trip.domain.fixture.ParticipantFixture; -import org.junit.jupiter.api.Test; - -import java.util.UUID; - -import static com.retrip.trip.domain.fixture.TripFixture.*; -import static org.junit.jupiter.api.Assertions.*; - -class ParticipantsTest { - - @Test - public void 리더가_아니라면_업데이트를_할_수_없다() { - // given - Trip trip = createTrip(TRIP_ID); - Participants participants = new Participants(LEADER_ID, trip, 4); - participants.addParticipant(Participant.createTripParticipant(MEMBER_ID, trip)); - - // when - boolean result = participants.updatableByLeader(MEMBER_ID); - - // then - assertFalse(result); - } - - @Test - void 최대_참여_인원보다_많은_인원이_참여할_수_없다() { - // given - Trip trip = createTrip(TRIP_ID); - Participants participants = new Participants(LEADER_ID, trip, 2); - UUID member1 = UUID.fromString("33333333-3333-3333-3333-333333333333"); - - // when - participants.addParticipant(Participant.createTripParticipant(member1, trip)); - - // then - assertThrows( - ParticipantFullException.class, - () -> { - UUID member2 = UUID.fromString("44444444-4444-4444-4444-444444444444"); - participants.addParticipant(Participant.createTripParticipant(member2, trip)); - }); - } - - @Test - void 최대_참여_인원을_현재_참여_인원보다_적게_변경할_수_없다() { - // given - Trip trip = createTrip(TRIP_ID); - Participants participants = new Participants(LEADER_ID, trip, 3); - UUID member1 = UUID.fromString("33333333-3333-3333-3333-333333333333"); - participants.addParticipant(Participant.createTripParticipant(member1, trip)); - - // then - assertThrows( - InvalidValueException.class, - () -> participants.updateMaxParticipants(1, LEADER_ID)); - } - - @Test - void 최대_참여_인원은_1명_이상이어야_한다() { - // given - Trip trip = createTrip(TRIP_ID); - - // then - assertThrows(InvalidValueException.class, () -> new Participants(LEADER_ID, trip, 0)); - } - - @Test - void 리더가_최대_참여_인원을_변경할_수_있다() { - // given - Trip trip = createTrip(TRIP_ID); - Participants participants = new Participants(LEADER_ID, trip, 3); - - // when - participants.updateMaxParticipants(5, LEADER_ID); - - // then - assertEquals(5, trip.getMaxParticipants()); - } - - @Test - void 현재_참여_인원수를_정확히_반환한다() { - // given - Trip trip = createTrip(TRIP_ID); - Participants participants = new Participants(LEADER_ID, trip, 3); - UUID member1 = UUID.fromString("33333333-3333-3333-3333-333333333333"); - participants.addParticipant(Participant.createTripParticipant(member1, trip)); - - // when - int currentCount = participants.getCurrentCount(); - - // then - assertEquals(2, currentCount); // 리더 + 참여자 1명 - } -} diff --git a/src/test/java/com/retrip/trip/domain/entity/TripDemandTest.java b/src/test/java/com/retrip/trip/domain/entity/TripDemandTest.java deleted file mode 100644 index e69de29..0000000 diff --git a/src/test/java/com/retrip/trip/domain/entity/TripTest.java b/src/test/java/com/retrip/trip/domain/entity/TripTest.java index ecbda55..9d7bddd 100644 --- a/src/test/java/com/retrip/trip/domain/entity/TripTest.java +++ b/src/test/java/com/retrip/trip/domain/entity/TripTest.java @@ -1,26 +1,23 @@ package com.retrip.trip.domain.entity; -import com.retrip.trip.domain.entity.participant.Participant; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.retrip.trip.domain.exception.NotLeaderException; import com.retrip.trip.domain.exception.PeriodUpdateFailedException; -import com.retrip.trip.domain.vo.ParticipantRole; +import com.retrip.trip.domain.fixture.ParticipantFixture; import com.retrip.trip.domain.vo.TripCategory; import com.retrip.trip.domain.vo.TripDescription; import com.retrip.trip.domain.vo.TripPeriod; import com.retrip.trip.domain.vo.TripTitle; -import java.util.List; - import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import java.time.LocalDate; import java.util.UUID; -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertThrows; - class TripTest { UUID memberId = UUID.fromString("c076d246-7e6d-4191-bf5c-310aebf4c003"); UUID destinationId = UUID.fromString("13c8ab91-76bc-4f70-93e9-89f1a65dc64a"); @@ -61,29 +58,6 @@ void createWithItinerary() { .doesNotThrowAnyException(); } - @Test - void 여행을_생성하면_생성자는_해당_여행에_리더가_된다() { - // given - Trip trip = - Trip.create( - destinationId, - new TripTitle("속초 여행 멤버 구함"), - new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), - new TripPeriod(LocalDate.now().plusDays(1), LocalDate.now().plusDays(5)), - true, - 4, - TripCategory.DOMESTIC); - - // when - List participants = trip.getParticipants().getValues(); - - // then - assertAll( - () -> assertThat(participants).hasSize(1), - () -> assertThat(participants.get(0).getRole()).isEqualTo(ParticipantRole.LEADER), - () -> assertThat(participants.get(0).getMemberId()).isEqualTo(memberId)); - } - @Test void 여행_일정이_없다면_균등_일정_생성으로_업데이트가_가능하다() { TripPeriod period = @@ -108,29 +82,4 @@ void createWithItinerary() { assertThat(trip.getItineraries().getValues().size()).isEqualTo(3); assertThat(trip.getItineraries().getValues().get(0).getName()).isEqualTo("day 1"); } - - @Test - void 여행_일정은_리더만_수정_가능하다() { - TripPeriod period = - new TripPeriod(LocalDate.now().plusDays(1), LocalDate.now().plusDays(6)); - Trip trip = - Trip.create( - UUID.randomUUID(), - new TripTitle("속초 여행 멤버 구함"), - new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), - period, - true, - 4, - TripCategory.DOMESTIC); - trip.addParticipant(Participant.createTripParticipant(memberId, trip)); - - // when - TripPeriod updateTripPeriod = - new TripPeriod(LocalDate.now().plusDays(2), LocalDate.now().plusDays(4)); - - // then - assertThrows( - PeriodUpdateFailedException.class, - () -> trip.updatePeriod(updateTripPeriod, memberId)); - } } diff --git a/src/test/java/com/retrip/trip/domain/entity/participant/ParticipantTest.java b/src/test/java/com/retrip/trip/domain/entity/participant/ParticipantTest.java new file mode 100644 index 0000000..7eaa6a6 --- /dev/null +++ b/src/test/java/com/retrip/trip/domain/entity/participant/ParticipantTest.java @@ -0,0 +1,31 @@ +package com.retrip.trip.domain.entity.participant; + +import static com.retrip.trip.domain.fixture.TripFixture.*; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +import com.retrip.trip.domain.fixture.ParticipantFixture; +import com.retrip.trip.domain.vo.ParticipantRole; +import com.retrip.trip.domain.vo.ParticipantStatus; + +import java.util.UUID; + +import org.junit.jupiter.api.Test; + +class ParticipantTest { + @Test + void 여행_참여를_금지한다() { + Participant participant = ParticipantFixture.createParticipant(TRIP_ID, MEMBER_ID); + participant.ban(); + assertThat(participant.getStatus()).isEqualTo(ParticipantStatus.EXPELLED); + } + + @Test + void 사용자의_권한을_변경한다() { + Participant participant = ParticipantFixture.createParticipant(TRIP_ID, MEMBER_ID); + participant.changeRole(ParticipantRole.PARTICIPANT); + assertThat(participant.getRole().getViewName()) + .isEqualTo(ParticipantRole.PARTICIPANT.getViewName()); + } +} diff --git a/src/test/java/com/retrip/trip/domain/fixture/ParticipantFixture.java b/src/test/java/com/retrip/trip/domain/fixture/ParticipantFixture.java index fa103ed..9a2c129 100644 --- a/src/test/java/com/retrip/trip/domain/fixture/ParticipantFixture.java +++ b/src/test/java/com/retrip/trip/domain/fixture/ParticipantFixture.java @@ -17,7 +17,7 @@ public static Participant createLeaderParticipant(UUID tripId, UUID leaderId) { return Participant.create(tripId, leaderId, ParticipantRole.LEADER); } - public static Participant createParticipant(UUID tripId, UUID leaderId) { - return Participant.create(tripId, leaderId, ParticipantRole.PARTICIPANT); + public static Participant createParticipant(UUID tripId, UUID participantId) { + return Participant.create(tripId, participantId, ParticipantRole.PARTICIPANT); } } diff --git a/src/test/java/com/retrip/trip/domain/service/InvitationPolicyTest.java b/src/test/java/com/retrip/trip/domain/service/InvitationPolicyTest.java index 4202d6b..615b5f5 100644 --- a/src/test/java/com/retrip/trip/domain/service/InvitationPolicyTest.java +++ b/src/test/java/com/retrip/trip/domain/service/InvitationPolicyTest.java @@ -5,6 +5,7 @@ import com.retrip.trip.domain.entity.invitation.Invitation; import com.retrip.trip.domain.exception.*; import com.retrip.trip.domain.exception.common.IllegalStateException; +import com.retrip.trip.domain.fixture.ParticipantFixture; import com.retrip.trip.domain.vo.InvitationStatus; import com.retrip.trip.domain.vo.ParticipantRole; import com.retrip.trip.domain.vo.TripStatus; @@ -27,32 +28,6 @@ class InvitationPolicyTest { InvitationPolicy invitationPolicy = new InvitationPolicy(); - @Test - void 리더가_아닌_멤버가_사용자를_초대하면_예외가_발생한다() { - // given - Trip trip = createTrip(TRIP_ID); - Participant participant = Participant.create(trip.getId(), 혁진_ID, ParticipantRole.PARTICIPANT) - - List memberIds = List.of(정수_ID, 홍석_ID, 준호_ID); - - // when, then - assertThatThrownBy(() -> invitationPolicy.canInvite(trip, 혁진_ID, memberIds)) - .isExactlyInstanceOf(NotLeaderException.class); - } - - @Test - void 이미_여행_멤버인_사용자를_초대하면_예외가_발생한다() { - // given - Trip trip = createTrip(TRIP_ID); - trip.addParticipant(Participant.createTripParticipant(혁진_ID, trip)); - trip.addParticipant(Participant.createTripParticipant(지수_ID, trip)); - List memberIds = List.of(혁진_ID); - - // when, then - assertThatThrownBy(() -> invitationPolicy.canInvite(trip, LEADER_ID, memberIds)) - .isExactlyInstanceOf(TripInvitationDuplicateException.class); - } - @ParameterizedTest @EnumSource( mode = INCLUDE, @@ -64,7 +39,7 @@ class InvitationPolicyTest { List memberIds = List.of(혁진_ID); // when, then - assertThatThrownBy(() -> invitationPolicy.canInvite(trip, LEADER_ID, memberIds)) + assertThatThrownBy(() -> invitationPolicy.canInvite(trip)) .isExactlyInstanceOf(IllegalStateException.class); } @@ -109,21 +84,6 @@ class InvitationPolicyTest { .isExactlyInstanceOf(TripNotRecruitingException.class); } - @Test - void 여행이_최대인원이_채워진_경우_수락되지_않는다() { - // given - Trip trip = createTrip(TRIP_ID); - trip.addParticipant(Participant.createTripParticipant(혁진_ID, trip)); - trip.addParticipant(Participant.createTripParticipant(지수_ID, trip)); - trip.addParticipant(Participant.createTripParticipant(준호_ID, trip)); - - Invitation invitation = new Invitation(TRIP_ID, MEMBER_ID); - - // when, then - assertThatThrownBy(() -> invitationPolicy.canAccept(trip, invitation)) - .isExactlyInstanceOf(TripParticipantsIsFullException.class); - } - @ParameterizedTest @EnumSource( mode = EXCLUDE, diff --git a/src/test/java/com/retrip/trip/domain/service/ParticipantPolicyTest.java b/src/test/java/com/retrip/trip/domain/service/ParticipantPolicyTest.java index f08dafe..7597854 100644 --- a/src/test/java/com/retrip/trip/domain/service/ParticipantPolicyTest.java +++ b/src/test/java/com/retrip/trip/domain/service/ParticipantPolicyTest.java @@ -1,16 +1,28 @@ package com.retrip.trip.domain.service; +import static com.retrip.trip.domain.exception.common.ErrorCode.TRIP_MEMBER_BANNED_CANNOT_APPLY; +import static com.retrip.trip.domain.exception.common.ErrorCode.TRIP_MEMBER_NOT_IN_TRIP; import static com.retrip.trip.domain.fixture.TripFixture.*; import static com.retrip.trip.domain.fixture.TripFixture.TRIP_ID; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import com.retrip.trip.domain.entity.Trip; import com.retrip.trip.domain.entity.participant.Participant; import com.retrip.trip.domain.exception.NotLeaderException; +import com.retrip.trip.domain.exception.NotParticipantException; import com.retrip.trip.domain.exception.ParticipantFullException; +import com.retrip.trip.domain.exception.TripInvitationDuplicateException; +import com.retrip.trip.domain.exception.common.BusinessException; +import com.retrip.trip.domain.exception.common.InvalidValueException; import com.retrip.trip.domain.fixture.ParticipantFixture; +import com.retrip.trip.domain.vo.ParticipantRole; + +import java.util.List; +import java.util.UUID; + import org.junit.jupiter.api.Test; class ParticipantPolicyTest { @@ -27,7 +39,7 @@ class ParticipantPolicyTest { } @Test - void 리더가_아니면_업데이트를_할_수_없다() { + void 리더가_아니면_오류_발생() { // given Trip trip = createTrip(TRIP_ID); Participant participant = ParticipantFixture.createParticipant(trip.getId(), MEMBER_ID); @@ -36,4 +48,104 @@ class ParticipantPolicyTest { assertThatThrownBy(() -> participantPolicy.validateLeader(participant.getRole())) .isExactlyInstanceOf(NotLeaderException.class); } + + @Test + void 참여자가_아니면_오류_발생() { + // given + Trip trip = createTrip(TRIP_ID); + Participant participant = + ParticipantFixture.createLeaderParticipant(trip.getId(), MEMBER_ID); + + // when, then + assertThatThrownBy(() -> participantPolicy.validateParticipant(participant.getRole())) + .isExactlyInstanceOf(NotParticipantException.class); + } + + @Test + void 자기_자신에게_리더를_위임할_수_없다() { + // given + Trip trip = createTrip(TRIP_ID); + Participant leader = ParticipantFixture.createLeaderParticipant(trip.getId(), MEMBER_ID); + + // when, then + assertThatThrownBy(() -> participantPolicy.validateDelegate(leader, leader)) + .isExactlyInstanceOf(InvalidValueException.class); + } + + @Test + void 리더가_아니면_위임할_수_없다() { + // given + Trip trip = createTrip(TRIP_ID); + Participant participant = ParticipantFixture.createParticipant(trip.getId(), MEMBER_ID); + Participant leader = + ParticipantFixture.createLeaderParticipant(trip.getId(), UUID.randomUUID()); + + // when, then + assertThatThrownBy(() -> participantPolicy.validateDelegate(participant, leader)) + .isExactlyInstanceOf(NotLeaderException.class); + } + + @Test + void 리더를_위임할_수_있다() { + // given + Trip trip = createTrip(TRIP_ID); + Participant participant = ParticipantFixture.createParticipant(trip.getId(), MEMBER_ID); + Participant leader = + ParticipantFixture.createLeaderParticipant(trip.getId(), UUID.randomUUID()); + + // when, then + participantPolicy.changeLeader(leader, participant); + assertThat(leader.getRole().getViewName()) + .isEqualTo(ParticipantRole.PARTICIPANT.getViewName()); + assertThat(participant.getRole().getViewName()) + .isEqualTo(ParticipantRole.LEADER.getViewName()); + } + + @Test + void 이미_여행_멤버인_사용자를_초대하면_예외가_발생한다() { + // given + Trip trip = createTrip(TRIP_ID); + Participant leader = ParticipantFixture.createLeaderParticipant(TRIP_ID, MEMBER_ID); + Participant participant1 = ParticipantFixture.createParticipant(TRIP_ID, 혁진_ID); + Participant participant2 = ParticipantFixture.createParticipant(TRIP_ID, 지수_ID); + + List memberIds = List.of(혁진_ID); + + // when, then + assertThatThrownBy( + () -> + participantPolicy.validateInvite( + List.of(leader, participant1, participant2), memberIds)) + .isExactlyInstanceOf(TripInvitationDuplicateException.class); + } + + @Test + void 여행에_참여하지_않은_사용자를_참여_금지할_수_없다() { + // given + Trip trip = createTrip(TRIP_ID); + Participant leader = ParticipantFixture.createLeaderParticipant(trip.getId(), MEMBER_ID); + Participant participant = + ParticipantFixture.createParticipant(UUID.randomUUID(), UUID.randomUUID()); + + // when, then + assertThatThrownBy( + () -> + participantPolicy.validateBan( + List.of(leader), List.of(participant.getMemberId()))) + .isExactlyInstanceOf(BusinessException.class); + } + + @Test + void 금지상태인_사용자는_여행_요청을_할_수_없다() { + // given + Trip trip = createTrip(TRIP_ID); + Participant leader = ParticipantFixture.createLeaderParticipant(trip.getId(), MEMBER_ID); + Participant participant = + ParticipantFixture.createParticipant(trip.getId(), UUID.randomUUID()); + participant.ban(); + + // when, then + assertThatThrownBy(() -> participantPolicy.validateDemand(participant)) + .isExactlyInstanceOf(BusinessException.class); + } } From ce5ff867c847e5b4adcbee98b45098d6456b5d16 Mon Sep 17 00:00:00 2001 From: junhokim Date: Tue, 29 Jul 2025 12:38:05 +0900 Subject: [PATCH 3/3] =?UTF-8?q?feat:=203=EC=B0=A8=20=EB=A6=AC=ED=8E=99?= =?UTF-8?q?=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 테스트 코드 수정 - Approve 비지니스 수정 --- .../in/service/ParticipantService.java | 21 +++-- .../application/in/service/TripService.java | 10 ++- .../in/usecase/ParticipantManageUseCase.java | 4 +- .../in/usecase/TripDemandUseCase.java | 5 +- .../ParticipantQueryRepository.java | 2 + .../com/retrip/trip/domain/entity/Trip.java | 1 + .../domain/service/ParticipantPolicy.java | 10 ++- .../in/presentation/rest/TripController.java | 5 +- .../query/ParticipantQuerydslRepository.java | 9 +++ .../application/in/InvitationServiceTest.java | 44 +++++++--- .../in/ParticipantServiceTest.java | 53 +++++++----- .../trip/application/in/TripServiceTest.java | 81 +++++++++++-------- .../domain/service/ParticipantPolicyTest.java | 27 ++++++- 13 files changed, 189 insertions(+), 83 deletions(-) diff --git a/src/main/java/com/retrip/trip/application/in/service/ParticipantService.java b/src/main/java/com/retrip/trip/application/in/service/ParticipantService.java index 6cff31c..f230936 100644 --- a/src/main/java/com/retrip/trip/application/in/service/ParticipantService.java +++ b/src/main/java/com/retrip/trip/application/in/service/ParticipantService.java @@ -13,6 +13,7 @@ import com.retrip.trip.domain.vo.ParticipantRole; import java.util.List; +import java.util.Optional; import java.util.UUID; import lombok.RequiredArgsConstructor; @@ -49,7 +50,7 @@ public Long findByMemberIdTotalCount(UUID memberId) { @Override public Participant createParticipant(UUID tripId, UUID memberId, int maxParticipants) { Long currentCount = participantQueryRepository.findByTripIdCount(memberId); - participantPolicy.validate(maxParticipants, currentCount); + participantPolicy.validate(maxParticipants, currentCount + 1); return participantRepository.save( Participant.create(tripId, memberId, ParticipantRole.PARTICIPANT)); } @@ -122,11 +123,17 @@ public List banMembers(UUID tripId, UUID loginMemberId, List } @Override - public void canDemand(UUID tripId, UUID memberId) { - Participant participant = - participantQueryRepository - .findByTripIdAndMemberId(tripId, memberId) - .orElseThrow(EntityNotFoundException::new); - participantPolicy.validateDemand(participant); + public void canDemand(UUID tripId, UUID memberId, int maxParticipants) { + Long count = participantQueryRepository.findByTripIdCount(tripId); + Optional participant = + participantQueryRepository.findByTripIdAndMemberIdAndAllStatus(tripId, memberId); + participantPolicy.validateDemand(participant, count, maxParticipants); + } + + @Override + public void canUpdateMaxParticipant(UUID tripId, UUID memberId, int maxParticipants) { + requireLeaderOrElseThrow(tripId, memberId); + Long currentCount = participantQueryRepository.findByTripIdCount(tripId); + participantPolicy.validate(maxParticipants, currentCount); } } diff --git a/src/main/java/com/retrip/trip/application/in/service/TripService.java b/src/main/java/com/retrip/trip/application/in/service/TripService.java index a778ae4..8eaee6d 100644 --- a/src/main/java/com/retrip/trip/application/in/service/TripService.java +++ b/src/main/java/com/retrip/trip/application/in/service/TripService.java @@ -66,7 +66,7 @@ public Page getTrips(Pageable page) { @Override public TripDemandResponse tripDemand(UUID tripId, TripDemandRequest request) { Trip trip = findTrip(tripId); - participantService.canDemand(tripId, request.memberId()); + participantService.canDemand(tripId, request.memberId(), trip.getMaxParticipants()); trip.addDemand(TripDemand.create(request.memberId(), trip, request.message())); return TripDemandResponse.of(trip.getTripDemands().getValues().getLast()); } @@ -76,12 +76,13 @@ private Trip findTrip(UUID tripId) { } @Override - public TripDemandApproveResponse approve(UUID memberId, UUID tripId, UUID tripDemandId) { + public TripDemandApproveResponse approve( + UUID memberId, UUID approverMemberId, UUID tripId, UUID tripDemandId) { TripDemand tripDemand = findTripDemandByTripIdAndTripDemandId(tripId, tripDemandId); participantService.requireLeaderOrElseThrow(tripId, memberId); tripDemand.approve(); participantService.createParticipant( - tripId, memberId, tripDemand.getTrip().getMaxParticipants()); + tripId, approverMemberId, tripDemand.getTrip().getMaxParticipants()); return TripDemandApproveResponse.of(tripDemand); } @@ -155,7 +156,8 @@ public DelegateLeaderResponse delegateLeader(UUID tripId, DelegateLeaderRequest public MaxParticipantUpdateResponse updateMaxParticipants( UUID tripId, MaxParticipantUpdateRequest request) { Trip trip = findTrip(tripId); - participantService.requireLeaderOrElseThrow(tripId, request.memberId()); + participantService.canUpdateMaxParticipant( + tripId, request.memberId(), request.maxParticipants()); trip.updateMaxParticipants(request.maxParticipants()); return MaxParticipantUpdateResponse.of(trip.getId(), trip.getMaxParticipants()); } diff --git a/src/main/java/com/retrip/trip/application/in/usecase/ParticipantManageUseCase.java b/src/main/java/com/retrip/trip/application/in/usecase/ParticipantManageUseCase.java index 5a6af05..b4216bb 100644 --- a/src/main/java/com/retrip/trip/application/in/usecase/ParticipantManageUseCase.java +++ b/src/main/java/com/retrip/trip/application/in/usecase/ParticipantManageUseCase.java @@ -28,5 +28,7 @@ public interface ParticipantManageUseCase { List banMembers(UUID tripId, UUID loginMemberId, List memberIds); - void canDemand(UUID tripId, UUID memberId); + void canDemand(UUID tripId, UUID memberId, int maxParticipants); + + void canUpdateMaxParticipant(UUID tripId, UUID memberId, int maxParticipants); } diff --git a/src/main/java/com/retrip/trip/application/in/usecase/TripDemandUseCase.java b/src/main/java/com/retrip/trip/application/in/usecase/TripDemandUseCase.java index f9c2e42..71dac89 100644 --- a/src/main/java/com/retrip/trip/application/in/usecase/TripDemandUseCase.java +++ b/src/main/java/com/retrip/trip/application/in/usecase/TripDemandUseCase.java @@ -11,9 +11,10 @@ public interface TripDemandUseCase { TripDemandResponse tripDemand(UUID tripId, TripDemandRequest request); - TripDemandApproveResponse approve(UUID memberId, UUID tripId, UUID tripDemandId); + TripDemandApproveResponse approve( + UUID memberId, UUID approverMemberId, UUID tripId, UUID tripDemandId); TripDemandRejectResponse reject(UUID memberId, UUID tripId, UUID tripDemandId); void banMembers(UUID memberId, UUID tripId, List memberIds); -} \ No newline at end of file +} diff --git a/src/main/java/com/retrip/trip/application/out/repository/ParticipantQueryRepository.java b/src/main/java/com/retrip/trip/application/out/repository/ParticipantQueryRepository.java index f17e0c6..081bde7 100644 --- a/src/main/java/com/retrip/trip/application/out/repository/ParticipantQueryRepository.java +++ b/src/main/java/com/retrip/trip/application/out/repository/ParticipantQueryRepository.java @@ -19,4 +19,6 @@ public interface ParticipantQueryRepository { Long findByTripIdCount(UUID tripId); Optional findByTripIdAndMemberId(UUID tripId, UUID memberId); + + Optional findByTripIdAndMemberIdAndAllStatus(UUID tripId, UUID memberId); } diff --git a/src/main/java/com/retrip/trip/domain/entity/Trip.java b/src/main/java/com/retrip/trip/domain/entity/Trip.java index 6900dcf..7b3e1e6 100644 --- a/src/main/java/com/retrip/trip/domain/entity/Trip.java +++ b/src/main/java/com/retrip/trip/domain/entity/Trip.java @@ -141,6 +141,7 @@ public void canDelegateLeader() { } public void updateMaxParticipants(int maxParticipants) { + validateMaxParticipants(maxParticipants); this.maxParticipants = maxParticipants; } diff --git a/src/main/java/com/retrip/trip/domain/service/ParticipantPolicy.java b/src/main/java/com/retrip/trip/domain/service/ParticipantPolicy.java index 2b7a560..d0f547f 100644 --- a/src/main/java/com/retrip/trip/domain/service/ParticipantPolicy.java +++ b/src/main/java/com/retrip/trip/domain/service/ParticipantPolicy.java @@ -23,7 +23,7 @@ public class ParticipantPolicy { public void validate(int maxParticipants, Long currentCount) { - if (maxParticipants <= currentCount + 1) { + if (maxParticipants <= currentCount) { throw new ParticipantFullException(); } } @@ -66,8 +66,12 @@ public void validateBan(List participants, List memberIds) { } } - public void validateDemand(Participant participant) { - if (participant.getStatus().isExpelled()) { + public void validateDemand(Optional participant, Long count, int maxParticipants) { + if (count.intValue() + 1 >= maxParticipants) { + throw new ParticipantFullException(); + } + + if (participant.isPresent() && participant.get().getStatus().isExpelled()) { throw new BusinessException(TRIP_MEMBER_BANNED_CANNOT_APPLY); } } diff --git a/src/main/java/com/retrip/trip/infra/adapter/in/presentation/rest/TripController.java b/src/main/java/com/retrip/trip/infra/adapter/in/presentation/rest/TripController.java index a8ba388..d803d53 100644 --- a/src/main/java/com/retrip/trip/infra/adapter/in/presentation/rest/TripController.java +++ b/src/main/java/com/retrip/trip/infra/adapter/in/presentation/rest/TripController.java @@ -81,11 +81,12 @@ public ResponseEntity updatePeriod( @PutMapping("/{tripId}/demand/{tripDemandId}/approve") @Schema(description = "여행 참가 신청 승인") public ApiResponse approveRequest( - @RequestParam("memberId") UUID memberId, // TODO: 추후 로그인 구현되면 이부분은 바뀔 에정 + @RequestParam("memberId") UUID memberId, // 승인하는 자 + @RequestParam("approveMemberId") UUID approverMemberId, // 승인 받는자 @PathVariable("tripId") UUID tripId, @PathVariable("tripDemandId") UUID tripDemandId) { TripDemandApproveResponse response = - tripDemandUseCase.approve(memberId, tripId, tripDemandId); + tripDemandUseCase.approve(memberId, approverMemberId, tripId, tripDemandId); return ApiResponse.ok(response); } diff --git a/src/main/java/com/retrip/trip/infra/adapter/out/persistence/mysql/query/ParticipantQuerydslRepository.java b/src/main/java/com/retrip/trip/infra/adapter/out/persistence/mysql/query/ParticipantQuerydslRepository.java index 65c931e..1d1d129 100644 --- a/src/main/java/com/retrip/trip/infra/adapter/out/persistence/mysql/query/ParticipantQuerydslRepository.java +++ b/src/main/java/com/retrip/trip/infra/adapter/out/persistence/mysql/query/ParticipantQuerydslRepository.java @@ -65,4 +65,13 @@ public Optional findByTripIdAndMemberId(UUID tripId, UUID memberId) ) .fetchOne()); } + + @Override + public Optional findByTripIdAndMemberIdAndAllStatus(UUID tripId, UUID memberId) { + return Optional.ofNullable( + query.select(participant) + .from(participant) + .where(participant.tripId.eq(tripId), participant.memberId.eq(memberId)) + .fetchOne()); + } } diff --git a/src/test/java/com/retrip/trip/application/in/InvitationServiceTest.java b/src/test/java/com/retrip/trip/application/in/InvitationServiceTest.java index 44a4fbb..733ddb5 100644 --- a/src/test/java/com/retrip/trip/application/in/InvitationServiceTest.java +++ b/src/test/java/com/retrip/trip/application/in/InvitationServiceTest.java @@ -8,6 +8,8 @@ import com.retrip.trip.application.in.response.InvitationsResponse; import com.retrip.trip.domain.entity.Trip; import com.retrip.trip.domain.entity.invitation.Invitation; +import com.retrip.trip.domain.entity.participant.Participant; +import com.retrip.trip.domain.fixture.ParticipantFixture; import org.junit.jupiter.api.Test; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -22,13 +24,24 @@ import static org.assertj.core.api.Assertions.assertThat; class InvitationServiceTest extends BaseInvitationServiceTest { + private Participant createLeader(UUID tripId, UUID memberId) { + return participantRepository.save( + ParticipantFixture.createLeaderParticipant(tripId, memberId)); + } + + private Participant createParticipant(UUID tripId, UUID newMemberId) { + return participantRepository.save( + ParticipantFixture.createParticipant(tripId, newMemberId)); + } + @Test void 여행_초대를_생성한다() { Trip trip = createTrip(TRIP_ID); tripRepository.save(trip); + createLeader(TRIP_ID, LEADER_ID); - TripInvitationsCreateRequest request = new TripInvitationsCreateRequest( - LEADER_ID, List.of(정수_ID, 홍석_ID, 준호_ID)); + TripInvitationsCreateRequest request = + new TripInvitationsCreateRequest(LEADER_ID, List.of(정수_ID, 홍석_ID, 준호_ID)); InvitationsCreateResponse response = invitationService.createInvitations(TRIP_ID, request); assertThat(response.tripId()).isEqualTo(TRIP_ID); assertThat(response.invitations().size()).isEqualTo(3); @@ -39,15 +52,17 @@ class InvitationServiceTest extends BaseInvitationServiceTest { // given Trip trip = createTrip(TRIP_ID); tripRepository.save(trip); + createLeader(TRIP_ID, LEADER_ID); - TripInvitationsCreateRequest request = new TripInvitationsCreateRequest( - LEADER_ID, List.of(정수_ID, 홍석_ID, 준호_ID)); + TripInvitationsCreateRequest request = + new TripInvitationsCreateRequest(LEADER_ID, List.of(정수_ID, 홍석_ID, 준호_ID)); invitationService.createInvitations(TRIP_ID, request); Pageable pageable = PageRequest.of(0, 10); // when Page invitations = - invitationService.getTripInvitations(TRIP_ID, LEADER_ID, INVITED.name(), pageable, DATE, "desc"); + invitationService.getTripInvitations( + TRIP_ID, LEADER_ID, INVITED.name(), pageable, DATE, "desc"); // then assertThat(invitations.getTotalElements()).isEqualTo(3); @@ -58,20 +73,29 @@ class InvitationServiceTest extends BaseInvitationServiceTest { // given UUID tripId1 = UUID.randomUUID(); tripRepository.save(createTrip(tripId1)); + createLeader(tripId1, LEADER_ID); + UUID tripId2 = UUID.randomUUID(); tripRepository.save(createTrip(tripId2)); + createLeader(tripId2, LEADER_ID); + UUID tripId3 = UUID.randomUUID(); tripRepository.save(createTrip(tripId3)); + createLeader(tripId3, LEADER_ID); - invitationService.createInvitations(tripId1, new TripInvitationsCreateRequest(LEADER_ID, List.of(홍석_ID))); - invitationService.createInvitations(tripId2, new TripInvitationsCreateRequest(LEADER_ID, List.of(홍석_ID))); - invitationService.createInvitations(tripId3, new TripInvitationsCreateRequest(LEADER_ID, List.of(홍석_ID))); + invitationService.createInvitations( + tripId1, new TripInvitationsCreateRequest(LEADER_ID, List.of(홍석_ID))); + invitationService.createInvitations( + tripId2, new TripInvitationsCreateRequest(LEADER_ID, List.of(홍석_ID))); + invitationService.createInvitations( + tripId3, new TripInvitationsCreateRequest(LEADER_ID, List.of(홍석_ID))); Pageable pageable = PageRequest.of(0, 10); // when Page invitations = - invitationService.getMemberInvitations(홍석_ID, INVITED.name(), pageable, DATE, "desc"); + invitationService.getMemberInvitations( + 홍석_ID, INVITED.name(), pageable, DATE, "desc"); // then assertThat(invitations.getTotalElements()).isEqualTo(3); @@ -82,6 +106,8 @@ class InvitationServiceTest extends BaseInvitationServiceTest { // given Trip trip = createTrip(TRIP_ID); tripRepository.save(trip); + createLeader(TRIP_ID, MEMBER_ID); + Invitation invitation = new Invitation(TRIP_ID, MEMBER_ID); invitationRepository.save(invitation); diff --git a/src/test/java/com/retrip/trip/application/in/ParticipantServiceTest.java b/src/test/java/com/retrip/trip/application/in/ParticipantServiceTest.java index f475a66..77624c9 100644 --- a/src/test/java/com/retrip/trip/application/in/ParticipantServiceTest.java +++ b/src/test/java/com/retrip/trip/application/in/ParticipantServiceTest.java @@ -11,11 +11,15 @@ import com.retrip.trip.domain.entity.Trip; import com.retrip.trip.domain.entity.participant.Participant; import com.retrip.trip.domain.exception.NotLeaderException; +import com.retrip.trip.domain.exception.NotParticipantException; import com.retrip.trip.domain.fixture.ParticipantFixture; import com.retrip.trip.domain.vo.ParticipantRole; import com.retrip.trip.domain.vo.ParticipantStatus; import jakarta.transaction.Transactional; + +import java.util.UUID; + import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; @@ -23,7 +27,15 @@ import java.util.List; class ParticipantServiceTest extends BaseInvitationServiceTest { - @Autowired private ParticipantService participantService; + private Participant createLeader(UUID tripId, UUID memberId) { + return participantRepository.save( + ParticipantFixture.createLeaderParticipant(tripId, memberId)); + } + + private Participant createParticipant(UUID tripId, UUID newMemberId) { + return participantRepository.save( + ParticipantFixture.createParticipant(tripId, newMemberId)); + } @Test void 리더_참여자를_생성한다() { @@ -52,14 +64,14 @@ class ParticipantServiceTest extends BaseInvitationServiceTest { @Test void 내가_참여중인_여행을_볼_수_있다() { // given - participantService.createParticipant(TRIP_ID, MEMBER_ID, 10); - participantService.createParticipant(TRIP_ID, MEMBER_ID, 10); - participantService.createParticipant(TRIP_ID, MEMBER_ID, 10); - participantService.createParticipant(TRIP_ID, MEMBER_ID, 10); + Participant leader = createLeader(TRIP_ID, LEADER_ID); + createParticipant(UUID.randomUUID(), LEADER_ID); + createParticipant(UUID.randomUUID(), LEADER_ID); + createParticipant(UUID.randomUUID(), LEADER_ID); // when List response = - participantService.findByMemberId(MEMBER_ID, PageRequest.of(0, 10)); + participantService.findByMemberId(LEADER_ID, PageRequest.of(0, 10)); // then assertThat(response.size()).isEqualTo(4); @@ -67,13 +79,13 @@ class ParticipantServiceTest extends BaseInvitationServiceTest { @Test void 내가_참여중인_여행을_총_갯수를_볼_수_있다() { - participantService.createParticipant(TRIP_ID, MEMBER_ID, 10); - participantService.createParticipant(TRIP_ID, MEMBER_ID, 10); - participantService.createParticipant(TRIP_ID, MEMBER_ID, 10); - participantService.createParticipant(TRIP_ID, MEMBER_ID, 10); + Participant leader = createLeader(TRIP_ID, LEADER_ID); + createParticipant(UUID.randomUUID(), LEADER_ID); + createParticipant(UUID.randomUUID(), LEADER_ID); + createParticipant(UUID.randomUUID(), LEADER_ID); // when - Long response = participantService.findByMemberIdTotalCount(MEMBER_ID); + Long response = participantService.findByMemberIdTotalCount(LEADER_ID); // then assertThat(response).isEqualTo(4); @@ -83,7 +95,8 @@ class ParticipantServiceTest extends BaseInvitationServiceTest { public void 리더는_정상_동작() { // given Trip trip = createTrip(TRIP_ID); - Participant leader = ParticipantFixture.createLeaderParticipant(TRIP_ID, LEADER_ID); + Participant leader = createLeader(TRIP_ID, LEADER_ID); + Participant participant = createParticipant(TRIP_ID, 준호_ID); // when // then @@ -97,7 +110,8 @@ class ParticipantServiceTest extends BaseInvitationServiceTest { public void 리더가_아니면_오류_발생() { // given Trip trip = createTrip(TRIP_ID); - Participant participant = ParticipantFixture.createParticipant(TRIP_ID, MEMBER_ID); + Participant leader = createLeader(TRIP_ID, LEADER_ID); + Participant participant = createParticipant(TRIP_ID, 준호_ID); // when // then @@ -112,7 +126,8 @@ class ParticipantServiceTest extends BaseInvitationServiceTest { public void 참여자는_정상_동작() { // given Trip trip = createTrip(TRIP_ID); - Participant participant = ParticipantFixture.createParticipant(TRIP_ID, LEADER_ID); + Participant leader = createLeader(TRIP_ID, LEADER_ID); + Participant participant = createParticipant(TRIP_ID, 준호_ID); // when // then @@ -126,12 +141,12 @@ class ParticipantServiceTest extends BaseInvitationServiceTest { public void 참여자가_아니면_오류_발생() { // given Trip trip = createTrip(TRIP_ID); - Participant leader = ParticipantFixture.createLeaderParticipant(TRIP_ID, MEMBER_ID); + Participant leader = createLeader(TRIP_ID, LEADER_ID); // when // then assertThrows( - NotLeaderException.class, + NotParticipantException.class, () -> participantService.requireParticipantOrElseThrow( trip.getId(), leader.getMemberId())); @@ -141,8 +156,8 @@ class ParticipantServiceTest extends BaseInvitationServiceTest { public void 리더를_위임_한다() { // given Trip trip = createTrip(TRIP_ID); - Participant leader = ParticipantFixture.createLeaderParticipant(TRIP_ID, LEADER_ID); - Participant participant = ParticipantFixture.createParticipant(TRIP_ID, LEADER_ID); + Participant leader = createLeader(TRIP_ID, LEADER_ID); + Participant participant = createParticipant(TRIP_ID, 준호_ID); // when Participant response = @@ -158,7 +173,7 @@ class ParticipantServiceTest extends BaseInvitationServiceTest { public void 방장은_여행에_참여하는_인원을_수락할_수_있다() { // given Trip trip = createTrip(TRIP_ID); - Participant leader = ParticipantFixture.createLeaderParticipant(TRIP_ID, LEADER_ID); + Participant leader = createLeader(TRIP_ID, LEADER_ID); // when ; diff --git a/src/test/java/com/retrip/trip/application/in/TripServiceTest.java b/src/test/java/com/retrip/trip/application/in/TripServiceTest.java index e9dea3c..998514f 100644 --- a/src/test/java/com/retrip/trip/application/in/TripServiceTest.java +++ b/src/test/java/com/retrip/trip/application/in/TripServiceTest.java @@ -171,7 +171,7 @@ private Trip createProgressTrip(UUID leaderId) { void 리더가_참여_요청을_승인하면_실제_참여자로_등록된다() { // given Trip newTrip = TripFixture.createTestTrip("승인 테스트 여행", "여행 설명", TripCategory.DOMESTIC); - Participant leader = ParticipantFixture.createLeaderParticipant(newTrip.getId(), MEMBER_ID); + Participant leader = createLeader(newTrip.getId(), MEMBER_ID); TripDemand tripDemand = TripDemand.create(newMemberId, newTrip, "참여 요청 메시지"); newTrip.getTripDemands().getValues().add(tripDemand); @@ -179,7 +179,7 @@ private Trip createProgressTrip(UUID leaderId) { // then TripDemandApproveResponse response = - tripService.approve(memberId, newTrip.getId(), tripDemand.getId()); + tripService.approve(MEMBER_ID, newMemberId, newTrip.getId(), tripDemand.getId()); Participant newParticipantMember = participantQueryRepository .findByTripIdAndMemberId(newTrip.getId(), newMemberId) @@ -195,24 +195,29 @@ private Trip createProgressTrip(UUID leaderId) { void 리더가_아니면_참여_요청을_승인할_수_없다() { // given Trip newTrip = TripFixture.createTestTrip("승인 테스트 여행", "여행 설명", TripCategory.DOMESTIC); - Participant leader = ParticipantFixture.createLeaderParticipant(newTrip.getId(), MEMBER_ID); + Participant leader = createLeader(newTrip.getId(), MEMBER_ID); + Participant participant = createParticipant(newTrip.getId(), UUID.randomUUID()); TripDemand tripDemand = TripDemand.create(newMemberId, newTrip, "참여 요청 메시지"); newTrip.getTripDemands().getValues().add(tripDemand); tripRepository.save(newTrip); - tripService.approve(memberId, newTrip.getId(), tripDemand.getId()); // then && when assertThrows( BusinessException.class, - () -> tripService.approve(newMemberId, newTrip.getId(), tripDemand.getId())); + () -> + tripService.approve( + participant.getMemberId(), + newMemberId, + newTrip.getId(), + tripDemand.getId())); } @Test void 리더가_참여_요청을_거절하면_요청_상태가_거절로_변경된다() { // given Trip newTrip = TripFixture.createTestTrip("거절 테스트 여행", "여행 설명", TripCategory.DOMESTIC); - Participant leader = ParticipantFixture.createLeaderParticipant(newTrip.getId(), MEMBER_ID); + Participant leader = createLeader(newTrip.getId(), MEMBER_ID); TripDemand tripDemand = TripDemand.create(newMemberId, newTrip, "참여 요청 메시지"); newTrip.getTripDemands().getValues().add(tripDemand); @@ -220,7 +225,7 @@ private Trip createProgressTrip(UUID leaderId) { // then TripDemandRejectResponse response = - tripService.reject(memberId, newTrip.getId(), tripDemand.getId()); + tripService.reject(MEMBER_ID, newTrip.getId(), tripDemand.getId()); // when assertThat(response).isNotNull(); @@ -231,11 +236,12 @@ private Trip createProgressTrip(UUID leaderId) { void 리더가_아니면_참여_요청을_거절할_수_없다() { // given Trip newTrip = TripFixture.createTestTrip("거절 테스트 여행", "여행 설명", TripCategory.DOMESTIC); - Participant leader = ParticipantFixture.createLeaderParticipant(newTrip.getId(), MEMBER_ID); + Participant leader = createLeader(newTrip.getId(), MEMBER_ID); + TripDemand tripDemand = TripDemand.create(newMemberId, newTrip, "참여 요청 메시지"); newTrip.getTripDemands().getValues().add(tripDemand); tripRepository.save(newTrip); - tripService.approve(memberId, newTrip.getId(), tripDemand.getId()); + tripService.approve(MEMBER_ID, newMemberId, newTrip.getId(), tripDemand.getId()); // then && when assertThrows( @@ -247,11 +253,12 @@ private Trip createProgressTrip(UUID leaderId) { void 빈_여행_일정을_수정한다() { // given Trip trip = createTestTrip("테스트 여행", "여행 설명", TripCategory.DOMESTIC); + Participant leader = createLeader(trip.getId(), MEMBER_ID); // then LocalDate start = LocalDate.now().plusDays(1); LocalDate end = LocalDate.now().plusDays(3); - PeriodUpdateRequest request = TripRequestFixture.createPeriod(memberId, start, end); + PeriodUpdateRequest request = TripRequestFixture.createPeriod(MEMBER_ID, start, end); PeriodUpdateResponse response = tripService.updatePeriod(trip.getId(), request); // when @@ -276,11 +283,11 @@ void updatePeriodIsBeforePrePeriodStart() { true, 4, TripCategory.DOMESTIC)); - + Participant leader = createLeader(trip.getId(), MEMBER_ID); // when PeriodUpdateRequest request = TripRequestFixture.createPeriod( - memberId, LocalDate.now().plusDays(1), LocalDate.now().plusDays(3)); + MEMBER_ID, LocalDate.now().plusDays(1), LocalDate.now().plusDays(3)); PeriodUpdateResponse response = tripService.updatePeriod(trip.getId(), request); // then @@ -316,11 +323,11 @@ void updatePeriodBetweenPrePeriodStartBeforeAndPrePeriodEndBefore() { true, 4, TripCategory.DOMESTIC)); - + Participant leader = createLeader(trip.getId(), MEMBER_ID); // when PeriodUpdateRequest request = TripRequestFixture.createPeriod( - memberId, LocalDate.now().plusDays(3), LocalDate.now().plusDays(8)); + MEMBER_ID, LocalDate.now().plusDays(3), LocalDate.now().plusDays(8)); PeriodUpdateResponse response = tripService.updatePeriod(trip.getId(), request); // then @@ -359,12 +366,12 @@ void updatePeriodBetweenPrePeriodStartBeforeAndPrePeriodEndAfter() { true, 4, TripCategory.DOMESTIC)); - + Participant leader = createLeader(trip.getId(), MEMBER_ID); // when PeriodUpdateRequest request = TripRequestFixture.createPeriod( - memberId, LocalDate.now().plusDays(3), LocalDate.now().plusDays(8)); + MEMBER_ID, LocalDate.now().plusDays(3), LocalDate.now().plusDays(8)); PeriodUpdateResponse response = tripService.updatePeriod(trip.getId(), request); // then @@ -403,11 +410,11 @@ void updatePeriodBetweenPrePeriodStartAfterAndPrePeriodEndBefore() { true, 4, TripCategory.DOMESTIC)); - + Participant leader = createLeader(trip.getId(), MEMBER_ID); // when PeriodUpdateRequest request = TripRequestFixture.createPeriod( - memberId, LocalDate.now().plusDays(7), LocalDate.now().plusDays(9)); + MEMBER_ID, LocalDate.now().plusDays(7), LocalDate.now().plusDays(9)); PeriodUpdateResponse response = tripService.updatePeriod(trip.getId(), request); // then @@ -443,11 +450,12 @@ void updatePeriodBetweenPrePeriodStartAfterAndPrePeriodEndAfter() { true, 4, TripCategory.DOMESTIC)); + Participant leader = createLeader(trip.getId(), MEMBER_ID); // when PeriodUpdateRequest request = TripRequestFixture.createPeriod( - memberId, LocalDate.now().plusDays(7), LocalDate.now().plusDays(12)); + MEMBER_ID, LocalDate.now().plusDays(7), LocalDate.now().plusDays(12)); PeriodUpdateResponse response = tripService.updatePeriod(trip.getId(), request); // then @@ -487,11 +495,12 @@ void updatePeriodIsAfterPrePeriodEndAfter() { true, 4, TripCategory.DOMESTIC)); + Participant leader = createLeader(trip.getId(), MEMBER_ID); // when PeriodUpdateRequest request = TripRequestFixture.createPeriod( - memberId, LocalDate.now().plusDays(11), LocalDate.now().plusDays(14)); + MEMBER_ID, LocalDate.now().plusDays(11), LocalDate.now().plusDays(14)); PeriodUpdateResponse response = tripService.updatePeriod(trip.getId(), request); // then @@ -538,14 +547,14 @@ void canLeaveTrip_success_forMember() { void canLeaveTrip_fail_forLeader() { // given Trip trip = createReadyTrip(); - Participant leader = createLeader(trip.getId(), memberId); + Participant leader = createLeader(trip.getId(), MEMBER_ID); Participant participant = createParticipant(trip.getId(), newMemberId); // when & then assertThrows( NotParticipantException.class, () -> { - tripService.leaveTrip(trip.getId(), memberId); + tripService.leaveTrip(trip.getId(), MEMBER_ID); }); } @@ -657,21 +666,21 @@ void getMyTrips_afterLeaving() { void 리더는_참여자들을_추방할_수_있다() { // given Trip newTrip = TripFixture.createTestTrip("승인 테스트 여행", "여행 설명", TripCategory.DOMESTIC); - Participant leader = createLeader(newTrip.getId(), memberId); + Participant leader = createLeader(newTrip.getId(), MEMBER_ID); TripDemand tripDemand = TripDemand.create(newMemberId, newTrip, "참여 요청 메시지"); newTrip.getTripDemands().getValues().add(tripDemand); tripRepository.save(newTrip); TripDemandApproveResponse response = - tripService.approve(memberId, newTrip.getId(), tripDemand.getId()); + tripService.approve(MEMBER_ID, newMemberId, newTrip.getId(), tripDemand.getId()); // then - tripService.banMembers(memberId, newTrip.getId(), List.of(newMemberId)); + tripService.banMembers(MEMBER_ID, newTrip.getId(), List.of(newMemberId)); Trip trip = tripRepository.findById(newTrip.getId()).orElseThrow(); Participant banParticipant = participantQueryRepository - .findByTripIdAndMemberId(trip.getId(), newMemberId) + .findByTripIdAndMemberIdAndAllStatus(trip.getId(), newMemberId) .orElseThrow(); // when @@ -683,12 +692,12 @@ void getMyTrips_afterLeaving() { void 해당_여행에_강퇴당한_사용자는_다시_참여요청할_수_없다() { // given Trip newTrip = TripFixture.createTestTrip("승인 테스트 여행", "여행 설명", TripCategory.DOMESTIC); - Participant leader = createLeader(newTrip.getId(), memberId); + Participant leader = createLeader(newTrip.getId(), MEMBER_ID); TripDemand tripDemand = TripDemand.create(newMemberId, newTrip, "참여 요청 메시지"); newTrip.getTripDemands().getValues().add(tripDemand); tripRepository.save(newTrip); - tripService.approve(memberId, newTrip.getId(), tripDemand.getId()); - tripService.banMembers(memberId, newTrip.getId(), List.of(newMemberId)); + tripService.approve(MEMBER_ID, newMemberId, newTrip.getId(), tripDemand.getId()); + tripService.banMembers(MEMBER_ID, newTrip.getId(), List.of(newMemberId)); TripDemandRequest request = new TripDemandRequest(newMemberId, "강퇴당한 후 다시 참여 요청 메시지"); @@ -711,6 +720,7 @@ void tripIsFull_then_cannotJoin() { 1, TripCategory.DOMESTIC); tripRepository.save(trip); + Participant leader = createLeader(trip.getId(), MEMBER_ID); // when & then TripDemandRequest newRequest = new TripDemandRequest(UUID.randomUUID(), "저도 참여하고 싶어요!"); @@ -736,10 +746,10 @@ void leader_can_update_maxParticipants() { 3, TripCategory.DOMESTIC); tripRepository.save(trip); - Participant leader = createLeader(trip.getId(), memberId); + Participant leader = createLeader(trip.getId(), MEMBER_ID); // when - MaxParticipantUpdateRequest request = new MaxParticipantUpdateRequest(5, memberId); + MaxParticipantUpdateRequest request = new MaxParticipantUpdateRequest(5, MEMBER_ID); MaxParticipantUpdateResponse response = tripService.updateMaxParticipants(trip.getId(), request); @@ -760,7 +770,8 @@ void nonLeader_cannot_update_maxParticipants() { true, 3, TripCategory.DOMESTIC); - Participant leader = createLeader(trip.getId(), memberId); + tripRepository.save(trip); + Participant leader = createLeader(trip.getId(), MEMBER_ID); UUID unknownMember = UUID.randomUUID(); Participant participant = createParticipant(trip.getId(), unknownMember); @@ -790,15 +801,15 @@ void cannot_update_maxParticipants_lessThan_currentParticipants() { 4, TripCategory.DOMESTIC); tripRepository.save(trip); - Participant leader = createLeader(trip.getId(), memberId); + Participant leader = createLeader(trip.getId(), MEMBER_ID); Participant participant = createParticipant(trip.getId(), UUID.randomUUID()); // when & then assertThrows( - InvalidValueException.class, + ParticipantFullException.class, () -> { tripService.updateMaxParticipants( - trip.getId(), new MaxParticipantUpdateRequest(2, memberId)); + trip.getId(), new MaxParticipantUpdateRequest(2, MEMBER_ID)); }); } } diff --git a/src/test/java/com/retrip/trip/domain/service/ParticipantPolicyTest.java b/src/test/java/com/retrip/trip/domain/service/ParticipantPolicyTest.java index 7597854..a29d93d 100644 --- a/src/test/java/com/retrip/trip/domain/service/ParticipantPolicyTest.java +++ b/src/test/java/com/retrip/trip/domain/service/ParticipantPolicyTest.java @@ -21,6 +21,7 @@ import com.retrip.trip.domain.vo.ParticipantRole; import java.util.List; +import java.util.Optional; import java.util.UUID; import org.junit.jupiter.api.Test; @@ -145,7 +146,31 @@ class ParticipantPolicyTest { participant.ban(); // when, then - assertThatThrownBy(() -> participantPolicy.validateDemand(participant)) + assertThatThrownBy( + () -> + participantPolicy.validateDemand( + Optional.of(participant), 2L, trip.getMaxParticipants())) .isExactlyInstanceOf(BusinessException.class); } + + @Test + void 참여중인_인원이_꽉찼을_경우_요청할_수_없다() { + // given + Trip trip = createTrip(TRIP_ID); + Participant leader = ParticipantFixture.createLeaderParticipant(trip.getId(), MEMBER_ID); + List participants = + List.of( + ParticipantFixture.createParticipant(trip.getId(), UUID.randomUUID()), + ParticipantFixture.createParticipant(trip.getId(), UUID.randomUUID()), + ParticipantFixture.createParticipant(trip.getId(), UUID.randomUUID())); + Participant newParticipant = + ParticipantFixture.createParticipant(trip.getId(), UUID.randomUUID()); + + // when, then + assertThatThrownBy( + () -> + participantPolicy.validateDemand( + Optional.empty(), (long) participants.size() + 1, 4)) + .isExactlyInstanceOf(ParticipantFullException.class); + } }