diff --git a/src/main/java/com/retrip/trip/application/in/TripService.java b/src/main/java/com/retrip/trip/application/in/TripService.java deleted file mode 100644 index e0a9351..0000000 --- a/src/main/java/com/retrip/trip/application/in/TripService.java +++ /dev/null @@ -1,120 +0,0 @@ -package com.retrip.trip.application.in; - -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.response.*; -import com.retrip.trip.application.in.usecase.*; -import com.retrip.trip.application.out.repository.*; -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.exception.TripNotFoundException; -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.Pageable; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; -import java.util.UUID; - -@RequiredArgsConstructor -@Transactional -@Service -public class TripService - 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; - - @Override - public TripCreateResponse createTrip(TripCreateRequest request) { - Trip trip = tripRepository.save(request.to()); - return TripCreateResponse.of(trip); - } - - @Override - public TripCreateResponse createTripWithItineraries(TripCreateRequest request) { - Trip trip = tripRepository.save(request.toWithItineraries()); - return TripCreateResponse.of(trip); - } - - @Transactional(readOnly = true) - @Override - public Page getTrips(Pageable page) { - return tripQueryRepository.findTrips(page); - } - - @Override - 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); - } - - @Override - public TripDemandApproveResponse approve(UUID memberId, UUID tripId, UUID tripDemandId) { - TripDemand tripDemand = findTripDemandByTripIdAndTripDemandId(tripId, tripDemandId); - tripDemand.approve(memberId); - return TripDemandApproveResponse.of(tripDemand); - } - - @Override - public TripDemandRejectResponse reject(UUID memberId, UUID tripId, UUID joinRequestId) { - TripDemand tripDemand = findTripDemandByTripIdAndTripDemandId(tripId, joinRequestId); - tripDemand.reject(memberId); - return TripDemandRejectResponse.of(tripDemand); - } - - private TripDemand findTripDemandByTripIdAndTripDemandId(UUID tripId, UUID tripDemandId) { - return tripDemandReadRepository - .findByTripIdAndId(tripId, tripDemandId) - .orElseThrow(() -> new EntityNotFoundException("참여 요청을 찾을 수 없습니다.")); - } - - @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.updatePeriod(period, request.memberId()); - return PeriodUpdateResponse.of(trip); - } - - @Transactional(readOnly = true) - @Override - public Page getMyTrips(UUID memberId, Pageable page) { - return tripQueryRepository.findMyTrips(memberId, page); - } - - @Override - public void banMembers(UUID loginMemberId, UUID tripId, List memberIds) { - Trip trip = findTrip(tripId); - trip.banMembers(loginMemberId, memberIds); - } - - @Override - public void leaveTrip(UUID tripId, UUID memberId) { - Trip trip = findTrip(tripId); - trip.leave(memberId); - tripRepository.save(trip); - } - - @Override - public DelegateLeaderResponse delegateLeader(UUID tripId, DelegateLeaderRequest request) { - Trip trip = findTrip(tripId); - 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/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/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/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/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..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 @@ -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,12 +29,16 @@ 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) { - Trip trip = findTripWithParticipants(tripId); - invitationPolicy.canInvite(trip, request.leaderId(), request.memberIds()); + public InvitationsCreateResponse createInvitations( + UUID tripId, TripInvitationsCreateRequest request) { + 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()); @@ -44,11 +48,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) { - invitationPolicy.canViewInvitations(findTripWithParticipants(tripId), leaderId); + UUID tripId, + UUID leaderId, + String status, + Pageable page, + TripInvitationOrder order, + String sort) { + participantService.requireLeaderOrElseThrow(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,35 +67,34 @@ 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) { - Trip trip = findTripWithParticipants(tripId); + public MemberInvitationAcceptResponse acceptMemberInvitations( + UUID memberId, UUID tripId, UUID invitationId) { + Trip trip = tripRepository.findById(tripId).orElseThrow(EntityNotFoundException::new); 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(); 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) + 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..f230936 --- /dev/null +++ b/src/main/java/com/retrip/trip/application/in/service/ParticipantService.java @@ -0,0 +1,139 @@ +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.NotParticipantException; +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.Optional; +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 +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 + @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); + } + + @Override + public Participant createParticipant(UUID tripId, UUID memberId, int maxParticipants) { + Long currentCount = participantQueryRepository.findByTripIdCount(memberId); + participantPolicy.validate(maxParticipants, currentCount + 1); + return participantRepository.save( + Participant.create(tripId, memberId, ParticipantRole.PARTICIPANT)); + } + + @Override + @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, 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 new file mode 100644 index 0000000..8eaee6d --- /dev/null +++ b/src/main/java/com/retrip/trip/application/in/service/TripService.java @@ -0,0 +1,164 @@ +package com.retrip.trip.application.in.service; + +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.*; +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.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; + +import java.util.List; +import java.util.UUID; + +@RequiredArgsConstructor +@Transactional +@Service +public class TripService + implements CreateTripUseCase, + GetTripUseCase, + TripDemandUseCase, + TripPeriodUseCase, + LeaveTripUseCase, + DelegateLeaderUseCase, + TripManagementUseCase { + private final TripRepository tripRepository; + private final TripQueryRepository tripQueryRepository; + private final TripItineraryQueryRepository tripItineraryQueryRepository; + private final TripDemandReadRepository tripDemandReadRepository; + private final ParticipantService participantService; + + @Override + public TripCreateResponse createTrip(TripCreateRequest request) { + Trip trip = tripRepository.save(request.to()); + 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()); + Participant participant = + participantService.createLeaderParticipant(trip.getId(), request.memberId()); + return TripCreateResponse.of(trip, participant); + } + + @Transactional(readOnly = true) + @Override + public Page getTrips(Pageable page) { + return tripQueryRepository.findTrips(page); + } + + @Override + public TripDemandResponse tripDemand(UUID tripId, TripDemandRequest request) { + Trip trip = findTrip(tripId); + participantService.canDemand(tripId, request.memberId(), trip.getMaxParticipants()); + trip.addDemand(TripDemand.create(request.memberId(), trip, request.message())); + return TripDemandResponse.of(trip.getTripDemands().getValues().getLast()); + } + + private Trip findTrip(UUID tripId) { + return tripRepository.findById(tripId).orElseThrow(TripNotFoundException::new); + } + + @Override + 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, approverMemberId, tripDemand.getTrip().getMaxParticipants()); + return TripDemandApproveResponse.of(tripDemand); + } + + @Override + public TripDemandRejectResponse reject(UUID memberId, UUID tripId, UUID joinRequestId) { + TripDemand tripDemand = findTripDemandByTripIdAndTripDemandId(tripId, joinRequestId); + participantService.requireLeaderOrElseThrow(tripId, memberId); + tripDemand.reject(); + return TripDemandRejectResponse.of(tripDemand); + } + + private TripDemand findTripDemandByTripIdAndTripDemandId(UUID tripId, UUID tripDemandId) { + return tripDemandReadRepository + .findByTripIdAndId(tripId, tripDemandId) + .orElseThrow(() -> new EntityNotFoundException("참여 요청을 찾을 수 없습니다.")); + } + + @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()); + participantService.requireLeaderOrElseThrow(tripId, request.memberId()); + trip.updatePeriod(period, request.memberId()); + return PeriodUpdateResponse.of(trip); + } + + @Transactional(readOnly = true) + @Override + 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 + public void banMembers(UUID loginMemberId, UUID tripId, List memberIds) { + Trip trip = findTrip(tripId); + trip.canBan(); + List participants = + participantService.banMembers(tripId, loginMemberId, memberIds); + } + + @Override + public void leaveTrip(UUID tripId, UUID memberId) { + Trip trip = findTrip(tripId); + trip.canLeave(); + participantService.requireParticipantOrElseThrow(tripId, memberId); + participantService.remove(tripId, memberId); + } + + @Override + public DelegateLeaderResponse delegateLeader(UUID tripId, DelegateLeaderRequest request) { + Trip trip = findTrip(tripId); + 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.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/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..b4216bb --- /dev/null +++ b/src/main/java/com/retrip/trip/application/in/usecase/ParticipantManageUseCase.java @@ -0,0 +1,34 @@ +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 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, 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/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/ParticipantQueryRepository.java b/src/main/java/com/retrip/trip/application/out/repository/ParticipantQueryRepository.java new file mode 100644 index 0000000..081bde7 --- /dev/null +++ b/src/main/java/com/retrip/trip/application/out/repository/ParticipantQueryRepository.java @@ -0,0 +1,24 @@ +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); + + Optional findByTripIdAndMemberIdAndAllStatus(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..a922080 --- /dev/null +++ b/src/main/java/com/retrip/trip/application/out/repository/ParticipantRepository.java @@ -0,0 +1,13 @@ +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 { + List findByTripId(UUID tripId); +} 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/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 3490d65..7b3e1e6 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,8 @@ 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.exception.common.BusinessException; +import com.retrip.trip.domain.exception.*; +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; @@ -17,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 @@ -29,113 +26,93 @@ 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 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())) { - throw new BusinessException(TRIP_MEMBER_BANNED_CANNOT_APPLY); + private static void validateMaxParticipants(int maxParticipants) { + if (maxParticipants < 1) { + throw new InvalidValueException( + ErrorCode.INVALID_MAX_PARTICIPANTS, "최대 참여 인원은 1명 이상이어야 합니다."); } } - public void validateParticipantLimitNotExceeded() { - tripParticipants.validateCanJoin(); - } - - 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); @@ -151,29 +128,26 @@ public List getItinerariesIds() { return getItineraries().ids(); } - public void banMembers(UUID loginMemberId, List memberIds) { - this.tripParticipants.banMembers(loginMemberId, memberIds, this); - } - - public void leave(UUID memberId) { + public void canLeave() { if (!this.status.canLeave()) { throw new TripNotReadyException(); } + } - TripParticipant participant = tripParticipants.findParticipantById(memberId) - .orElseThrow(() -> new NotParticipantException("현재 여행에 참여하고 있지 않습니다.")); - - if (participant.isLeader()) { - throw new LeaderCannotLeaveException(); + public void canDelegateLeader() { + if (this.status != TripStatus.BEFORE_TRIP) { + throw new TripNotReadyException(); } - tripParticipants.removeParticipant(memberId); } + public void updateMaxParticipants(int maxParticipants) { + validateMaxParticipants(maxParticipants); + this.maxParticipants = maxParticipants; + } - public void delegateLeader(UUID currentLeaderId, UUID newLeaderId) { - if (this.status != TripStatus.BEFORE_TRIP) { - throw new TripNotReadyException(); + public void canBan() { + if (!TripStatus.RECRUITING.equals(status)) { + throw new IllegalStateException("해당 여행은 모집 중이 아닙니다."); } - tripParticipants.delegateLeader(currentLeaderId, newLeaderId); } } 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..d438f37 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,37 +39,23 @@ 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)) { - 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/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 51% 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..b359ca4 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,45 +39,23 @@ 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 static TripParticipant createTripParticipant(UUID memberId, Trip trip) { - return new TripParticipant( - UUID.randomUUID(), - memberId, - ParticipantRole.PARTICIPANT, - ParticipantStatus.ACTIVE, - trip - ); - } - - public boolean isLeader() { - return this.role.isLeader(); + 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 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/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/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..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.TripParticipants; 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) { - TripParticipants participants = trip.getTripParticipants(); + public void canInvite(Trip trip) { if (trip.getStatus().cannotCreateInvitations()) { throw new IllegalStateException("여행 초대를 생성할 수 없는 상태입니다. " + trip.getStatus().name()); } - - if (isNotLeader(trip.getTripParticipants(), leaderId)) { - throw new MemberIsNotLeaderException(); - } - - if (participants.anyDuplicate(memberIds)) { - throw new TripInvitationDuplicateException("여행 멤버로 등록된 사용자는 초대할 수 없습니다."); - } - } - - public void canViewInvitations(Trip trip, UUID leaderId) { - if (isNotLeader(trip.getTripParticipants(), leaderId)) { - throw new MemberIsNotLeaderException(); - } - } - - public boolean isNotLeader(TripParticipants tripParticipants, UUID leaderId) { - return !tripParticipants.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(); } - TripParticipants tripParticipants = trip.getTripParticipants(); - if (tripParticipants.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 new file mode 100644 index 0000000..d0f547f --- /dev/null +++ b/src/main/java/com/retrip/trip/domain/service/ParticipantPolicy.java @@ -0,0 +1,78 @@ +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 +public class ParticipantPolicy { + + public void validate(int maxParticipants, Long currentCount) { + if (maxParticipants <= currentCount) { + throw new ParticipantFullException(); + } + } + + public void validateLeader(ParticipantRole role) { + if (!role.isLeader()) { + 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(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/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 b88ca16..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 @@ -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 = "여행 카테고리 목록 조회") @@ -79,28 +80,33 @@ 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, // 승인하는 자 + @RequestParam("approveMemberId") UUID approverMemberId, // 승인 받는자 + @PathVariable("tripId") UUID tripId, + @PathVariable("tripDemandId") UUID tripDemandId) { + TripDemandApproveResponse response = + tripDemandUseCase.approve(memberId, approverMemberId, 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 +114,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,18 +122,27 @@ 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(); } + + @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/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..1d1d129 --- /dev/null +++ b/src/main/java/com/retrip/trip/infra/adapter/out/persistence/mysql/query/ParticipantQuerydslRepository.java @@ -0,0 +1,77 @@ +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()); + } + + @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/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/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 new file mode 100644 index 0000000..77624c9 --- /dev/null +++ b/src/test/java/com/retrip/trip/application/in/ParticipantServiceTest.java @@ -0,0 +1,187 @@ +package com.retrip.trip.application.in; + +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.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; + +import java.util.List; + +class ParticipantServiceTest 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 리더_참여자를_생성한다() { + // 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 + Participant response = participantService.createParticipant(TRIP_ID, MEMBER_ID, 4); + + // when + + // then + assertDoesNotThrow(() -> participantService.remove(TRIP_ID, MEMBER_ID)); + } + + @Test + void 내가_참여중인_여행을_볼_수_있다() { + // given + 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(LEADER_ID, PageRequest.of(0, 10)); + + // then + assertThat(response.size()).isEqualTo(4); + } + + @Test + void 내가_참여중인_여행을_총_갯수를_볼_수_있다() { + 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(LEADER_ID); + + // then + assertThat(response).isEqualTo(4); + } + + @Test + public void 리더는_정상_동작() { + // given + Trip trip = createTrip(TRIP_ID); + Participant leader = createLeader(TRIP_ID, LEADER_ID); + Participant participant = createParticipant(TRIP_ID, 준호_ID); + + // when + // then + assertDoesNotThrow( + () -> + participantService.requireLeaderOrElseThrow( + trip.getId(), leader.getMemberId())); + } + + @Test + public void 리더가_아니면_오류_발생() { + // given + Trip trip = createTrip(TRIP_ID); + Participant leader = createLeader(TRIP_ID, LEADER_ID); + Participant participant = createParticipant(TRIP_ID, 준호_ID); + + // when + // then + assertThrows( + NotLeaderException.class, + () -> + participantService.requireLeaderOrElseThrow( + trip.getId(), participant.getMemberId())); + } + + @Test + public void 참여자는_정상_동작() { + // given + Trip trip = createTrip(TRIP_ID); + Participant leader = createLeader(TRIP_ID, LEADER_ID); + Participant participant = createParticipant(TRIP_ID, 준호_ID); + + // when + // then + assertDoesNotThrow( + () -> + participantService.requireParticipantOrElseThrow( + trip.getId(), participant.getMemberId())); + } + + @Test + public void 참여자가_아니면_오류_발생() { + // given + Trip trip = createTrip(TRIP_ID); + Participant leader = createLeader(TRIP_ID, LEADER_ID); + + // when + // then + assertThrows( + NotParticipantException.class, + () -> + participantService.requireParticipantOrElseThrow( + trip.getId(), leader.getMemberId())); + } + + @Test + public void 리더를_위임_한다() { + // given + Trip trip = createTrip(TRIP_ID); + Participant leader = createLeader(TRIP_ID, LEADER_ID); + Participant participant = createParticipant(TRIP_ID, 준호_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 = createLeader(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 e386342..998514f 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.Trip; import com.retrip.trip.domain.entity.TripDemand; -import com.retrip.trip.domain.entity.TripParticipant; +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,20 +28,25 @@ 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 = Trip.create( - memberId, UUID.randomUUID(), new TripTitle(title), new TripDescription(description), @@ -45,30 +57,30 @@ private Trip createTestTrip(String title, String description, TripCategory categ return tripRepository.save(trip); } - 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); + private Trip createReadyTrip() { + 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 +108,6 @@ private Trip createProgressTrip(UUID leaderId) { TripPeriod period = createFuturePeriod(); tripRepository.save( Trip.createWithItineraries( - memberId, UUID.randomUUID(), new TripTitle("속초 여행 멤버 구함"), new TripDescription("속초 여행은 이렇게이렇게 갈겁니다~"), @@ -106,7 +117,6 @@ private Trip createProgressTrip(UUID leaderId) { TripCategory.DOMESTIC)); tripRepository.save( Trip.createWithItineraries( - memberId, UUID.randomUUID(), new TripTitle("강릉 여행 멤버 구함"), new TripDescription("강릉 여행은 이렇게이렇게 갈겁니다~"), @@ -116,7 +126,6 @@ private Trip createProgressTrip(UUID leaderId) { TripCategory.DOMESTIC)); tripRepository.save( Trip.createWithItineraries( - memberId, UUID.randomUUID(), new TripTitle("대구 여행 멤버 구함"), new TripDescription("대구 여행은 이렇게이렇게 갈겁니다~"), @@ -126,7 +135,6 @@ private Trip createProgressTrip(UUID leaderId) { TripCategory.DOMESTIC)); tripRepository.save( Trip.createWithItineraries( - memberId, UUID.randomUUID(), new TripTitle("부산 여행 멤버 구함"), new TripDescription("부산 여행은 이렇게이렇게 갈겁니다~"), @@ -145,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 @@ -160,49 +170,62 @@ private Trip createProgressTrip(UUID leaderId) { @Test void 리더가_참여_요청을_승인하면_실제_참여자로_등록된다() { // given - Trip newTrip = TripFixture.createTestTrip(memberId, "승인 테스트 여행", "여행 설명", TripCategory.DOMESTIC); + Trip newTrip = TripFixture.createTestTrip("승인 테스트 여행", "여행 설명", TripCategory.DOMESTIC); + Participant leader = createLeader(newTrip.getId(), MEMBER_ID); + TripDemand tripDemand = TripDemand.create(newMemberId, newTrip, "참여 요청 메시지"); newTrip.getTripDemands().getValues().add(tripDemand); tripRepository.save(newTrip); // then - 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(); + TripDemandApproveResponse response = + tripService.approve(MEMBER_ID, newMemberId, newTrip.getId(), tripDemand.getId()); + 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 = 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())); + assertThrows( + BusinessException.class, + () -> + tripService.approve( + participant.getMemberId(), + newMemberId, + newTrip.getId(), + tripDemand.getId())); } @Test void 리더가_참여_요청을_거절하면_요청_상태가_거절로_변경된다() { // given - Trip newTrip = TripFixture.createTestTrip(memberId, "거절 테스트 여행", "여행 설명", TripCategory.DOMESTIC); + Trip newTrip = TripFixture.createTestTrip("거절 테스트 여행", "여행 설명", TripCategory.DOMESTIC); + Participant leader = createLeader(newTrip.getId(), MEMBER_ID); + 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(MEMBER_ID, newTrip.getId(), tripDemand.getId()); // when assertThat(response).isNotNull(); @@ -212,29 +235,30 @@ private Trip createProgressTrip(UUID leaderId) { @Test void 리더가_아니면_참여_요청을_거절할_수_없다() { // given - Trip newTrip = TripFixture.createTestTrip(memberId, "거절 테스트 여행", "여행 설명", TripCategory.DOMESTIC); + Trip newTrip = TripFixture.createTestTrip("거절 테스트 여행", "여행 설명", TripCategory.DOMESTIC); + 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(BusinessException.class, () -> tripService.reject(newMemberId, newTrip.getId(), tripDemand.getId())); + assertThrows( + BusinessException.class, + () -> tripService.reject(newMemberId, newTrip.getId(), tripDemand.getId())); } @Test 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 @@ -247,211 +271,211 @@ 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)); + Participant leader = createLeader(trip.getId(), MEMBER_ID); // when - PeriodUpdateRequest request = TripRequestFixture.createPeriod( - memberId, - LocalDate.now().plusDays(1), - LocalDate.now().plusDays(3) - ); + PeriodUpdateRequest request = + TripRequestFixture.createPeriod( + MEMBER_ID, 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)); + Participant leader = createLeader(trip.getId(), MEMBER_ID); // when - PeriodUpdateRequest request = TripRequestFixture.createPeriod( - memberId, - LocalDate.now().plusDays(3), - LocalDate.now().plusDays(8) - ); + PeriodUpdateRequest request = + TripRequestFixture.createPeriod( + MEMBER_ID, 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)); + Participant leader = createLeader(trip.getId(), MEMBER_ID); // when - PeriodUpdateRequest request = TripRequestFixture.createPeriod( - memberId, - LocalDate.now().plusDays(3), - LocalDate.now().plusDays(8) - ); + PeriodUpdateRequest request = + TripRequestFixture.createPeriod( + MEMBER_ID, 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)); + Participant leader = createLeader(trip.getId(), MEMBER_ID); // when - PeriodUpdateRequest request = TripRequestFixture.createPeriod( - memberId, - LocalDate.now().plusDays(7), - LocalDate.now().plusDays(9) - ); + PeriodUpdateRequest request = + TripRequestFixture.createPeriod( + MEMBER_ID, 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)); + Participant leader = createLeader(trip.getId(), MEMBER_ID); // when - PeriodUpdateRequest request = TripRequestFixture.createPeriod( - memberId, - LocalDate.now().plusDays(7), - LocalDate.now().plusDays(12) - ); + PeriodUpdateRequest request = + TripRequestFixture.createPeriod( + MEMBER_ID, 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,157 +483,180 @@ 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)); + Participant leader = createLeader(trip.getId(), MEMBER_ID); // when - PeriodUpdateRequest request = TripRequestFixture.createPeriod( - memberId, - LocalDate.now().plusDays(11), - LocalDate.now().plusDays(14) - ); + PeriodUpdateRequest request = + TripRequestFixture.createPeriod( + MEMBER_ID, 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 @DisplayName("멤버가 성공적으로 여행을 나간다") - void leaveTrip_success_forMember() { + void canLeaveTrip_success_forMember() { // given - Trip trip = createReadyTrip(memberId); - trip.addParticipant(TripParticipant.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.getTripParticipants().findParticipantById(newMemberId).isPresent(); + boolean isParticipantPresent = + 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(), MEMBER_ID); + Participant participant = createParticipant(trip.getId(), newMemberId); // when & then - assertThrows(LeaderCannotLeaveException.class, () -> { - tripService.leaveTrip(trip.getId(), memberId); - }); + assertThrows( + NotParticipantException.class, + () -> { + tripService.leaveTrip(trip.getId(), MEMBER_ID); + }); } @Test @DisplayName("여행이 '여행 전' 상태가 아니면 나갈 수 없다") - void leaveTrip_fail_whenTripNotReady() { + void canLeaveTrip_fail_whenTripNotReady() { // given Trip trip = createProgressTrip(memberId); - trip.addParticipant(TripParticipant.createTripParticipant(newMemberId, trip)); - tripRepository.save(trip); + Participant leader = createLeader(trip.getId(), memberId); + Participant participant = createParticipant(trip.getId(), newMemberId); // when & then - assertThrows(TripNotReadyException.class, () -> { - tripService.leaveTrip(trip.getId(), newMemberId); - }); + assertThrows( + TripNotReadyException.class, + () -> { + tripService.leaveTrip(trip.getId(), newMemberId); + }); } @Test @DisplayName("리더가 성공적으로 다른 멤버에게 리더를 위임한다") - void delegateLeader_success() { + void canDelegateLeader_success() { // given - Trip trip = createReadyTrip(memberId); - trip.addParticipant(TripParticipant.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.getTripParticipants().findParticipantById(newMemberId).get().isLeader()); - assertThat(updatedTrip.getTripParticipants().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(TripParticipant.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 - assertThrows(MemberIsNotLeaderException.class, () -> { - tripService.delegateLeader(trip.getId(), request); - }); + assertThrows( + NotLeaderException.class, + () -> { + tripService.delegateLeader(trip.getId(), request); + }); } @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 - assertThrows(InvalidValueException.class, () -> { - tripService.delegateLeader(trip.getId(), request); - }); + assertThrows( + InvalidValueException.class, + () -> { + tripService.delegateLeader(trip.getId(), request); + }); } @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(NotParticipantException.class, () -> { - tripService.delegateLeader(trip.getId(), request); - }); + assertThrows( + NotParticipantException.class, + () -> { + tripService.delegateLeader(trip.getId(), request); + }); } @Test @DisplayName("여행을 나간 후 '나의 여행 목록'에 보이지 않는다") void getMyTrips_afterLeaving() { // given - Trip trip = createReadyTrip(memberId); - trip.addParticipant(TripParticipant.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); - 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 +665,23 @@ void getMyTrips_afterLeaving() { @Test void 리더는_참여자들을_추방할_수_있다() { // given - Trip newTrip = TripFixture.createTestTrip(memberId, "승인 테스트 여행", "여행 설명", TripCategory.DOMESTIC); + Trip newTrip = TripFixture.createTestTrip("승인 테스트 여행", "여행 설명", TripCategory.DOMESTIC); + 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()); + TripDemandApproveResponse response = + 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(); - TripParticipant banParticipant = trip.getTripParticipants().getValues().stream() - .filter(participant -> participant.getMemberId().equals(newMemberId)) - .findFirst() - .orElseThrow(); + Participant banParticipant = + participantQueryRepository + .findByTripIdAndMemberIdAndAllStatus(trip.getId(), newMemberId) + .orElseThrow(); // when assertThat(response).isNotNull(); @@ -641,146 +691,125 @@ void getMyTrips_afterLeaving() { @Test void 해당_여행에_강퇴당한_사용자는_다시_참여요청할_수_없다() { // given - Trip newTrip = TripFixture.createTestTrip(memberId, "승인 테스트 여행", "여행 설명", TripCategory.DOMESTIC); + Trip newTrip = TripFixture.createTestTrip("승인 테스트 여행", "여행 설명", TripCategory.DOMESTIC); + 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, "강퇴당한 후 다시 참여 요청 메시지"); // 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); + Participant leader = createLeader(trip.getId(), MEMBER_ID); // 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); + Participant leader = createLeader(trip.getId(), MEMBER_ID); // when - int newMaxParticipants = 5; - trip.getTripParticipants().updateMaxParticipants(newMaxParticipants,memberId); - tripRepository.save(trip); + MaxParticipantUpdateRequest request = new MaxParticipantUpdateRequest(5, MEMBER_ID); + MaxParticipantUpdateResponse response = + tripService.updateMaxParticipants(trip.getId(), request); // then - Trip updatedTrip = tripRepository.findById(trip.getId()).get(); - assertThat(updatedTrip.getTripParticipants().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( - 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); tripRepository.save(trip); + Participant leader = createLeader(trip.getId(), MEMBER_ID); + UUID unknownMember = UUID.randomUUID(); + Participant participant = createParticipant(trip.getId(), unknownMember); - // when & then - assertThrows(MemberIsNotLeaderException.class, () -> { - trip.getTripParticipants().updateMaxParticipants(5, nonLeaderId); - }); + // when + MaxParticipantUpdateRequest request = new MaxParticipantUpdateRequest(5, unknownMember); + + // then + assertThrows( + NotLeaderException.class, + () -> { + tripService.updateMaxParticipants(trip.getId(), request); + }); } @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); tripRepository.save(trip); + Participant leader = createLeader(trip.getId(), MEMBER_ID); + Participant participant = createParticipant(trip.getId(), UUID.randomUUID()); // when & then - assertThrows(InvalidValueException.class, () -> { - trip.getTripParticipants().updateMaxParticipants(2,memberId); - }); - } - - @Test - @DisplayName("isLeader 메서드가 정확하게 리더와 멤버를 구분하는지 확인한다.") - 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)); - tripRepository.save(trip); - - // when - Trip savedTrip = tripRepository.findById(trip.getId()).get(); - boolean isLeaderResult = savedTrip.getTripParticipants().isLeader(memberId); - boolean isNotLeaderResult = savedTrip.getTripParticipants().isLeader(nonLeaderId); - - - // then - assertTrue(isLeaderResult); - assertThrows(InvalidValueException.class, () -> { - savedTrip.getTripParticipants().isLeader(UUID.randomUUID()); - }); + assertThrows( + ParticipantFullException.class, + () -> { + tripService.updateMaxParticipants( + trip.getId(), new MaxParticipantUpdateRequest(2, MEMBER_ID)); + }); } } 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/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/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/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..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,25 +1,23 @@ package com.retrip.trip.domain.entity; +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"); @@ -27,108 +25,61 @@ 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(); - } - - @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 - ); - - // when - List participants = trip.getTripParticipants().getValues(); - - // then - assertAll( - () -> assertThat(participants).hasSize(1), - () -> assertThat(participants.get(0).getRole()).isEqualTo(ParticipantRole.LEADER), - () -> assertThat(participants.get(0).getMemberId()).isEqualTo(memberId) - ); + 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 여행_일정이_없다면_균등_일정_생성으로_업데이트가_가능하다() { - 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); 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(), - 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)); - } } 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 new file mode 100644 index 0000000..9a2c129 --- /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 participantId) { + return Participant.create(tripId, participantId, 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..615b5f5 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,13 @@ 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.fixture.ParticipantFixture; 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; @@ -26,38 +28,10 @@ class InvitationPolicyTest { InvitationPolicy invitationPolicy = new InvitationPolicy(); - @Test - void 리더가_아닌_멤버가_사용자를_초대하면_예외가_발생한다() { - // given - Trip trip = createTrip(TRIP_ID); - TripParticipant participant = TripParticipant.createTripParticipant(혁진_ID, trip); - trip.addParticipant(participant); - List memberIds = List.of(정수_ID, 홍석_ID, 준호_ID); - - // when, then - assertThatThrownBy(() -> invitationPolicy.canInvite(trip, 혁진_ID, memberIds)) - .isExactlyInstanceOf(MemberIsNotLeaderException.class); - } - - @Test - void 이미_여행_멤버인_사용자를_초대하면_예외가_발생한다() { - // given - Trip trip = createTrip(TRIP_ID); - trip.addParticipant(TripParticipant.createTripParticipant(혁진_ID, trip)); - trip.addParticipant(TripParticipant.createTripParticipant(지수_ID, trip)); - List memberIds = List.of(혁진_ID); - - // when, then - assertThatThrownBy(() -> invitationPolicy.canInvite(trip, LEADER_ID, memberIds)) - .isExactlyInstanceOf(TripInvitationDuplicateException.class); - } - @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); @@ -65,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); } @@ -96,7 +70,9 @@ class InvitationPolicyTest { } @ParameterizedTest - @EnumSource(mode = EXCLUDE, names = {"RECRUITING"}) + @EnumSource( + mode = EXCLUDE, + names = {"RECRUITING"}) void 여행이_모집중이_아니면_수락되지_않는다(TripStatus status) { // given Trip trip = createTrip(TRIP_ID); @@ -108,23 +84,10 @@ class InvitationPolicyTest { .isExactlyInstanceOf(TripNotRecruitingException.class); } - @Test - 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)); - - Invitation invitation = new Invitation(TRIP_ID, MEMBER_ID); - - // when, then - assertThatThrownBy(() -> invitationPolicy.canAccept(trip, invitation)) - .isExactlyInstanceOf(TripParticipantsIsFullException.class); - } - @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..a29d93d --- /dev/null +++ b/src/test/java/com/retrip/trip/domain/service/ParticipantPolicyTest.java @@ -0,0 +1,176 @@ +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.Optional; +import java.util.UUID; + +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); + } + + @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( + 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); + } +}