diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/review/api/ReviewController.java b/src/main/java/org/ktc2/cokaen/wouldyouin/review/api/ReviewController.java index abf69bb3..98f03c50 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/review/api/ReviewController.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/review/api/ReviewController.java @@ -35,12 +35,12 @@ public class ReviewController { @GetMapping public ResponseEntity> getReviewsByMemberId( - @Authorize({MemberType.normal}) MemberIdentifier member, + @Authorize({MemberType.normal, MemberType.curator}) MemberIdentifier member, @RequestParam(defaultValue = ParamDefaults.PAGE) Integer page, @RequestParam(defaultValue = ParamDefaults.PAGE_SIZE) Integer size, @RequestParam(defaultValue = ParamDefaults.LAST_ID) Long lastId) { return ApiResponse.ok( - reviewService.getAllByMemberId(member.id(), lastId, PageRequest.of(page, size))); + reviewService.getAllByMemberId(member.id(), PageRequest.of(page, size), lastId)); } @GetMapping("/events/{eventId}") @@ -50,7 +50,7 @@ public ResponseEntity> getReviewsByEventId( @RequestParam(defaultValue = ParamDefaults.PAGE_SIZE) Integer size, @RequestParam(defaultValue = ParamDefaults.LAST_ID) Long lastId) { return ApiResponse.ok( - reviewService.getAllByEventId(eventId, lastId, PageRequest.of(page, size))); + reviewService.getAllByEventId(eventId, PageRequest.of(page, size), lastId)); } @GetMapping("/{reviewId}") @@ -61,16 +61,18 @@ public ResponseEntity> getReviewByReviewId( @GetMapping("events") public ResponseEntity> getUnreviewedEventsByMemberId( - @Authorize(MemberType.normal) MemberIdentifier identifier, + @Authorize({MemberType.normal, MemberType.curator}) MemberIdentifier identifier, @RequestParam(defaultValue = ParamDefaults.PAGE) Integer page, @RequestParam(defaultValue = ParamDefaults.PAGE_SIZE) Integer size, - @RequestParam(defaultValue = ParamDefaults.LAST_ID) Long lastId){ - return ApiResponse.ok(reviewService.getUnreviewedEventsByMemberId(identifier.id(), PageRequest.of(page, size), lastId)); + @RequestParam(defaultValue = ParamDefaults.LAST_ID) Long lastId) { + return ApiResponse.ok( + reviewService.getUnreviewedEventsByMemberId(identifier.id(), PageRequest.of(page, size), + lastId)); } @PostMapping public ResponseEntity> createReview( - @Authorize(MemberType.normal) MemberIdentifier member, + @Authorize({MemberType.normal, MemberType.curator}) MemberIdentifier member, @Valid @RequestBody ReviewCreateRequest reviewCreateRequest) { return ApiResponse.created(reviewService.create(member.id(), reviewCreateRequest)); } @@ -78,7 +80,7 @@ public ResponseEntity> createReview( @PutMapping("/{reviewId}") public ResponseEntity> updateReview( @PathVariable("reviewId") Long reviewId, - @Authorize(MemberType.normal) MemberIdentifier member, + @Authorize({MemberType.normal, MemberType.curator}) MemberIdentifier member, @Valid @RequestBody ReviewEditRequest reviewEditRequest) { return ApiResponse.ok(reviewService.update(member.id(), reviewId, reviewEditRequest)); } @@ -86,7 +88,7 @@ public ResponseEntity> updateReview( @DeleteMapping("/{reviewId}") public ResponseEntity> deleteReview( @PathVariable("reviewId") Long reviewId, - @Authorize(MemberType.normal) MemberIdentifier member) { + @Authorize({MemberType.normal, MemberType.curator}) MemberIdentifier member) { reviewService.delete(member.id(), reviewId); return ApiResponse.noContent(); } diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/review/api/dto/ReviewCreateRequest.java b/src/main/java/org/ktc2/cokaen/wouldyouin/review/api/dto/ReviewCreateRequest.java index a6653ef6..61fcb95c 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/review/api/dto/ReviewCreateRequest.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/review/api/dto/ReviewCreateRequest.java @@ -14,7 +14,7 @@ import org.ktc2.cokaen.wouldyouin.review.persist.Review; @Getter -@Builder +@Builder(toBuilder = true) @EqualsAndHashCode @ToString public class ReviewCreateRequest { @@ -22,12 +22,12 @@ public class ReviewCreateRequest { @NotNull(message = "이벤트 ID는 필수입니다.") private Long eventId; - @NotBlank(message = "별점은 필수입니다.") - @Min(value = 0, message = "별점은 0점 이상입니다. ") - @Max(value = 5, message = "별점은 5점 이하입니다. ") - private int score; + @NotNull(message = "별점은 필수입니다.") + @Min(value = 0, message = "별점은 0점 이상입니다.") + @Max(value = 5, message = "별점은 5점 이하입니다.") + private Integer score; - @NotBlank(message = "내용은 필수입니다. ") + @NotBlank(message = "내용은 필수입니다.") @Size(min = 5, max = 50, message = "내용은 5자 이상 50자 이하입니다.") private String content; diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/review/api/dto/ReviewEditRequest.java b/src/main/java/org/ktc2/cokaen/wouldyouin/review/api/dto/ReviewEditRequest.java index 6fc6d488..d0069102 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/review/api/dto/ReviewEditRequest.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/review/api/dto/ReviewEditRequest.java @@ -3,6 +3,7 @@ import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import lombok.Builder; import lombok.EqualsAndHashCode; @@ -15,12 +16,12 @@ @ToString public class ReviewEditRequest { - @NotBlank(message = "별점은 필수입니다.") - @Min(value = 0, message = "별점은 0점 이상입니다. ") - @Max(value = 5, message = "별점은 5점 이하입니다. ") + @NotNull(message = "별점은 필수입니다.") + @Min(value = 0, message = "별점은 0점 이상입니다.") + @Max(value = 5, message = "별점은 5점 이하입니다.") private Integer score; - @NotBlank(message = "내용은 필수입니다. ") + @NotBlank(message = "내용은 필수입니다.") @Size(min = 5, max = 50, message = "내용은 5자 이상 50자 이하입니다.") private String content; } diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/review/application/ReviewService.java b/src/main/java/org/ktc2/cokaen/wouldyouin/review/application/ReviewService.java index 94f9fe51..c2178751 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/review/application/ReviewService.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/review/application/ReviewService.java @@ -37,7 +37,7 @@ public ReviewResponse getById(Long reviewId) { } @Transactional(readOnly = true) - public ReviewSliceResponse getAllByMemberId(Long memberId, Long oldLastId, Pageable pageable) { + public ReviewSliceResponse getAllByMemberId(Long memberId, Pageable pageable, Long oldLastId) { Slice reviews = reviewRepository.findByMemberIdOrderByReviewIdDesc(memberId, oldLastId, pageable); Long newLastId = getLastId(reviews, oldLastId); @@ -45,7 +45,7 @@ public ReviewSliceResponse getAllByMemberId(Long memberId, Long oldLastId, Pagea } @Transactional(readOnly = true) - public ReviewSliceResponse getAllByEventId(Long eventId, Long oldLastId, Pageable pageable) { + public ReviewSliceResponse getAllByEventId(Long eventId, Pageable pageable, Long oldLastId) { Slice reviews = reviewRepository.findByEventIdOrderByReviewIdDesc(eventId, oldLastId, pageable); Long newLastId = getLastId(reviews, oldLastId); @@ -60,8 +60,10 @@ private Long getLastId(Slice reviews, Long oldLastId) { } @Transactional - public ReviewEventSliceResponse getUnreviewedEventsByMemberId(Long memberId, Pageable pageable, Long beforeLastId) { - Slice unreviewedEvents = reviewRepository.findUnreviewedEventsByMemberId(memberId, beforeLastId, pageable); + public ReviewEventSliceResponse getUnreviewedEventsByMemberId(Long memberId, Pageable pageable, + Long beforeLastId) { + Slice unreviewedEvents = reviewRepository.findUnreviewedEventsByMemberId(memberId, + beforeLastId, pageable); Long newLastId = EventService.getLastId(unreviewedEvents, beforeLastId); List responses = unreviewedEvents.stream() .map(ReviewEventResponse::from).toList(); diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/CommonData.java b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/CommonData.java index 9024bb7b..3112a3ba 100644 --- a/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/CommonData.java +++ b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/CommonData.java @@ -1,6 +1,8 @@ package org.ktc2.cokaen.wouldyouin._global.testdata; import org.ktc2.cokaen.wouldyouin._common.api.SliceInfo; +import org.ktc2.cokaen.wouldyouin._global.testdata.ReservationData.R.reservation1; +import org.ktc2.cokaen.wouldyouin._global.testdata.ReviewData.R.review1; public class CommonData { @@ -20,5 +22,25 @@ public static SliceInfo get() { .build(); } } + + public static class reservation { + + public static SliceInfo get() { + return SliceInfo.builder() + .sliceSize(10) + .lastId(reservation1.id) + .build(); + } + } + + public static class review { + + public static SliceInfo get() { + return SliceInfo.builder() + .sliceSize(10) + .lastId(review1.id) + .build(); + } + } } } \ No newline at end of file diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/EventData.java b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/EventData.java index c46c10d2..068d7988 100644 --- a/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/EventData.java +++ b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/EventData.java @@ -2,7 +2,6 @@ import java.time.LocalDateTime; import java.util.List; -import org.ktc2.cokaen.wouldyouin.image.persist.EventImage; import org.ktc2.cokaen.wouldyouin._common.vo.Area; import org.ktc2.cokaen.wouldyouin._common.vo.Category; import org.ktc2.cokaen.wouldyouin._common.vo.Location; @@ -11,7 +10,9 @@ import org.ktc2.cokaen.wouldyouin.event.api.dto.EventResponse; import org.ktc2.cokaen.wouldyouin.event.api.dto.relationResonse.CurationEventResponse; import org.ktc2.cokaen.wouldyouin.event.api.dto.relationResonse.ReservationEventResponse; +import org.ktc2.cokaen.wouldyouin.event.api.dto.relationResonse.ReviewEventResponse; import org.ktc2.cokaen.wouldyouin.event.persist.Event; +import org.ktc2.cokaen.wouldyouin.image.persist.EventImage; import org.ktc2.cokaen.wouldyouin.member.persist.Host; import org.springframework.test.util.ReflectionTestUtils; @@ -20,16 +21,36 @@ public class EventData { public static class R { public static class event1 { + + public static final long id = 201L; + public static final String title = "행사 제목"; + public static final String content = "행사 내용입니다. 행사의 내용은 최소 20자 최대 1000자 입니다."; + public static final Area area = Area.전체; + public static final Location location = new Location(55.0, 43.0, "광주 북구 용봉로 77"); + public static final LocalDateTime startTime = LocalDateTime.now().plusDays(3) + .plusHours(11).plusMinutes(47); + public static final LocalDateTime endTime = startTime.plusWeeks(2); + public static final Integer price = 15000; + public static final Integer totalSeat = 100; + public static final Integer leftSeat = 10; + public static final Category category = Category.밴드; + public static final String thumbnailUrl = ImageData.getThumbnailUrl( + _Relation.images().getFirst()); + public static final LocalDateTime createdDate = LocalDateTime.now().minusDays(1); + public static class _Relation { + public static Host host() { return MemberData.host1.entity.get(); } + public static List images() { return List.of( ImageData.event1.entity.get(), ImageData.event2.entity.get(), ImageData.event3.entity.get()); } + public static List imageUrls() { return List.of( ImageData.R.event1.url, @@ -39,11 +60,14 @@ public static List imageUrls() { } public static class _Dto { + public static class editRequest1 { + public static final String title = "modifiedTitle"; - public static final String content = "modifiedContent 조홍식씨 최소글자 20자라고 해놓고 안 지켰어요. "; + public static final String content = "modifiedContent 조홍식씨 최소글자 20자라고 해놓고 안 지켰어요."; public static final Area area = Area.광주; - public static final Location location = new Location(45.0, 143.0, "광주 북구 용봉로 77"); + public static final Location location = new Location(45.0, 143.0, + "광주 북구 용봉로 77"); public static final LocalDateTime startTime = event1.startTime.plusDays(2); public static final LocalDateTime endTime = startTime.plusWeeks(3); public static final int price = 20000; @@ -55,33 +79,39 @@ public static class editRequest1 { ); } } + } - public static final long id = 201L; + public static class event2 { + + public static final long id = 202L; public static final String title = "행사 제목"; public static final String content = "행사 내용입니다. 행사의 내용은 최소 20자 최대 1000자 입니다."; public static final Area area = Area.전체; public static final Location location = new Location(55.0, 43.0, "광주 북구 용봉로 77"); - public static final LocalDateTime startTime = LocalDateTime.now().plusDays(3).plusHours(11).plusMinutes(47); + public static final LocalDateTime startTime = LocalDateTime.now().plusDays(3) + .plusHours(11).plusMinutes(47); public static final LocalDateTime endTime = startTime.plusWeeks(2); public static final Integer price = 15000; public static final Integer totalSeat = 100; public static final Integer leftSeat = 10; public static final Category category = Category.밴드; - public static final String thumbnailUrl = ImageData.getThumbnailUrl(_Relation.images().getFirst()); + public static final String thumbnailUrl = ImageData.getThumbnailUrl( + _Relation.images().getFirst()); public static final LocalDateTime createdDate = LocalDateTime.now().minusDays(1); - } - public static class event2 { public static class _Relation { + public static Host host() { return MemberData.host1.entity.get(); } + public static List images() { return List.of( ImageData.event1.entity.get(), ImageData.event2.entity.get(), ImageData.event3.entity.get()); } + public static List imageUrls() { return List.of( ImageData.R.event1.url, @@ -91,11 +121,14 @@ public static List imageUrls() { } public static class _Dto { + public static class editRequest1 { + public static final String title = "modifiedTitle"; public static final String content = "modifiedContent 조홍식씨 최소글자 20자라고 해놓고 안 지켰어요. "; public static final Area area = Area.광주; - public static final Location location = new Location(45.0, 143.0, "광주 북구 용봉로 77"); + public static final Location location = new Location(45.0, 143.0, + "광주 북구 용봉로 77"); public static final LocalDateTime startTime = event1.startTime.plusDays(2); public static final LocalDateTime endTime = startTime.plusWeeks(3); public static final int price = 20000; @@ -107,25 +140,13 @@ public static class editRequest1 { ); } } - - public static final long id = 202L; - public static final String title = "행사 제목"; - public static final String content = "행사 내용입니다. 행사의 내용은 최소 20자 최대 1000자 입니다."; - public static final Area area = Area.전체; - public static final Location location = new Location(55.0, 43.0, "광주 북구 용봉로 77"); - public static final LocalDateTime startTime = LocalDateTime.now().plusDays(3).plusHours(11).plusMinutes(47); - public static final LocalDateTime endTime = startTime.plusWeeks(2); - public static final Integer price = 15000; - public static final Integer totalSeat = 100; - public static final Integer leftSeat = 10; - public static final Category category = Category.밴드; - public static final String thumbnailUrl = ImageData.getThumbnailUrl(_Relation.images().getFirst()); - public static final LocalDateTime createdDate = LocalDateTime.now().minusDays(1); } } public static class event1 { + public static class entity { + public static Event get() { Event ret = Event.builder() .title(R.event1.title) @@ -149,7 +170,9 @@ public static Event get() { return ret; } } + public static class entityWithNoHost { + public static Event get() { Event ret = Event.builder() .title(R.event1.title) @@ -172,8 +195,11 @@ public static Event get() { return ret; } } + public static class request { + public static class create { + public static EventCreateRequest get() { return EventCreateRequest.builder() .title(R.event1.title) @@ -185,11 +211,14 @@ public static EventCreateRequest get() { .price(R.event1.price) .totalSeat(R.event1.totalSeat) .category(R.event1.category) - .imageIds(R.event1._Relation.images().stream().map(EventImage::getId).toList()) + .imageIds( + R.event1._Relation.images().stream().map(EventImage::getId).toList()) .build(); } } + public static class edit1 { + public static EventEditRequest get() { return EventEditRequest.builder() .title(R.event1._Dto.editRequest1.title) @@ -206,21 +235,34 @@ public static EventEditRequest get() { } } } + public static class response { + public static EventResponse get() { - return EventResponse.from(EventData.event1.entity.get(), R.event1._Relation.imageUrls()); + return EventResponse.from(EventData.event1.entity.get(), + R.event1._Relation.imageUrls()); } } } public static class response { + public static class reservationEvent { + public static ReservationEventResponse createValidReservationEventResponse() { return ReservationEventResponse.from(EventData.event1.entity.get()); } } + public static class reviewEvent { + + public static ReviewEventResponse createValidReviewEventResponse() { + return ReviewEventResponse.from(EventData.event1.entity.get()); + } + } + public static class curationEvent { + public static CurationEventResponse createValidCurationEventResponse() { return CurationEventResponse.from(EventData.event1.entity.get()); } diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/MemberData.java b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/MemberData.java index ab59ae1b..9c95335a 100644 --- a/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/MemberData.java +++ b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/MemberData.java @@ -10,6 +10,7 @@ import org.ktc2.cokaen.wouldyouin.like.persist.CuratorLike; import org.ktc2.cokaen.wouldyouin.like.persist.HostLike; import org.ktc2.cokaen.wouldyouin.member.api.dto.relationResponse.CurationCuratorResponse; +import org.ktc2.cokaen.wouldyouin.member.api.dto.relationResponse.ReviewMemberResponse; import org.ktc2.cokaen.wouldyouin.member.api.dto.relationResponse.ReservationMemberResponse; import org.ktc2.cokaen.wouldyouin.member.persist.AccountType; import org.ktc2.cokaen.wouldyouin.member.persist.Curator; @@ -295,7 +296,11 @@ public static ReservationMemberResponse get() { return ReservationMemberResponse.from(normal1.entity.get()); } } - + public static class review1Member1 { + public static ReviewMemberResponse get(){ + return ReviewMemberResponse.from(normal1.entity.get()); + } + } public static class curation1Curator1 { public static CurationCuratorResponse get() { diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/ReservationData.java b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/ReservationData.java index 1f6789a4..649fd88f 100644 --- a/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/ReservationData.java +++ b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/ReservationData.java @@ -16,27 +16,33 @@ public class ReservationData { public static class R { + public static class reservation1 { + + public static final Long id = 1L; + public static final Integer price = _Relation.event().getPrice(); + public static final Integer quantity = 2; + public static final LocalDateTime reservationDate = LocalDateTime.of(2024, 3, 23, 0, 0); + public static final ReservationMemberResponse memberResponse = reservation1Member1.get(); + public static final ReservationEventResponse eventResponse = EventData.response.reservationEvent.createValidReservationEventResponse(); + public static class _Relation { + public static Member member() { return MemberData.normal1.entity.get(); } + public static Event event() { return EventData.event1.entity.get(); } } - public static final Long id = 1L; - public static final Integer price = _Relation.event().getPrice(); - public static final Integer quantity = 2; - public static final LocalDateTime reservationDate = LocalDateTime.of(2024, 3, 23, 0, 0); - - public static final ReservationMemberResponse memberResponse = reservation1Member1.get(); - public static final ReservationEventResponse eventResponse = EventData.response.reservationEvent.createValidReservationEventResponse(); } } public static class reservation1 { + public static class entity { + public static Reservation get() { Reservation ret = Reservation.builder() .member(R.reservation1._Relation.member()) @@ -45,11 +51,14 @@ public static Reservation get() { .quantity(R.reservation1.quantity) .build(); ReflectionTestUtils.setField(ret, "id", R.reservation1.id); - ReflectionTestUtils.setField(ret, "reservationDate", R.reservation1.reservationDate); + ReflectionTestUtils.setField(ret, "reservationDate", + R.reservation1.reservationDate); return ret; } } + public static class request { + public static ReservationRequest get() { return ReservationRequest.builder() .eventId(R.reservation1._Relation.event().getId()) @@ -57,21 +66,24 @@ public static ReservationRequest get() { .build(); } } + public static class response { + public static ReservationResponse get() { return ReservationResponse.from(reservation1.entity.get()); } } } + public static class sliceResponse { + public static ReservationSliceResponse get() { return ReservationSliceResponse.builder() .reservations(List.of( ReservationData.reservation1.response.get())) - .sliceInfo(CommonData.sliceInfo.curation.get()) + .sliceInfo(CommonData.sliceInfo.reservation.get()) .build(); } } - } diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/ReviewData.java b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/ReviewData.java new file mode 100644 index 00000000..26abee5e --- /dev/null +++ b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/ReviewData.java @@ -0,0 +1,109 @@ +package org.ktc2.cokaen.wouldyouin._global.testdata; + +import java.util.List; +import org.ktc2.cokaen.wouldyouin._global.testdata.MemberData.response.review1Member1; +import org.ktc2.cokaen.wouldyouin._global.testdata.ReviewData.R.review1._Dto.editRequest1; +import org.ktc2.cokaen.wouldyouin.event.api.dto.relationResonse.ReviewEventResponse; +import org.ktc2.cokaen.wouldyouin.event.persist.Event; +import org.ktc2.cokaen.wouldyouin.member.api.dto.relationResponse.ReviewMemberResponse; +import org.ktc2.cokaen.wouldyouin.member.persist.Member; +import org.ktc2.cokaen.wouldyouin.review.api.dto.ReviewCreateRequest; +import org.ktc2.cokaen.wouldyouin.review.api.dto.ReviewEditRequest; +import org.ktc2.cokaen.wouldyouin.review.api.dto.ReviewResponse; +import org.ktc2.cokaen.wouldyouin.review.api.dto.ReviewSliceResponse; +import org.ktc2.cokaen.wouldyouin.review.persist.Review; +import org.springframework.test.util.ReflectionTestUtils; + +public class ReviewData { + + public static class R { + + public static class review1 { + + public static final Long id = 1L; + public static final Integer score = 3; + public static final ReviewMemberResponse memberResponse = review1Member1.get(); + public static final ReviewEventResponse eventResponse = EventData.response.reviewEvent.createValidReviewEventResponse(); + private static final String content = "리뷰 내용입니다. 리뷰 내용은 5자 이상 50자 이하입니다"; + + public static class _Relation { + + public static Member member() { + return MemberData.normal1.entity.get(); + } + + public static Event event() { + return EventData.event1.entity.get(); + } + } + + public static class _Dto { + + public static class editRequest1 { + + public static final Integer score = 5; + public static final String content = "수정된 리뷰 내용입니다. 리뷰 내용은 5자 이상 50자 이하입니다"; + } + } + } + } + + public static class review1 { + + public static class entity { + + public static Review get() { + Review ret = Review.builder() + .member(R.review1._Relation.member()) + .event(R.review1._Relation.event()) + .score(R.review1.score) + .content(R.review1.content) + .build(); + ReflectionTestUtils.setField(ret, "id", R.review1.id); + return ret; + } + } + + public static class request { + + public static class create { + + public static ReviewCreateRequest get() { + return ReviewCreateRequest.builder() + .eventId(R.review1._Relation.event().getId()) + .score(R.review1.score) + .content(R.review1.content) + .build(); + } + } + + public static class edit1 { + + public static ReviewEditRequest get() { + return ReviewEditRequest.builder() + .score(editRequest1.score) + .content(editRequest1.content) + .build(); + } + } + } + + public static class response { + + public static ReviewResponse get() { + return ReviewResponse.from(review1.entity.get()); + } + } + } + + public static class sliceResponse { + + public static ReviewSliceResponse get() { + return ReviewSliceResponse.builder() + .reviews(List.of( + ReviewData.review1.response.get())) + .sliceInfo(CommonData.sliceInfo.review.get()) + .build(); + } + } +} diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/reservation/ReservationControllerUnitTest.java b/src/test/java/org/ktc2/cokaen/wouldyouin/reservation/ReservationControllerUnitTest.java index d356afdc..c28db0c5 100644 --- a/src/test/java/org/ktc2/cokaen/wouldyouin/reservation/ReservationControllerUnitTest.java +++ b/src/test/java/org/ktc2/cokaen/wouldyouin/reservation/ReservationControllerUnitTest.java @@ -43,21 +43,17 @@ @WebMvcTest(ReservationController.class) class ReservationControllerUnitTest { + private static final long randomId = abs(new Random().nextLong()); @Autowired private ObjectMapper objectMapper; - @MockBean private ReservationService reservationService; - @MockBean private JwtAuthFilter jwtAuthFilter; - @Autowired private MockMvc mockMvc; - @Autowired private WebApplicationContext context; - private static final long randomId = abs(new Random().nextLong()); @BeforeEach public void setup() throws Exception { @@ -155,7 +151,8 @@ void getReservationByEventId2() throws Exception { // then then(reservationService).should(times(1)).getAllByEventId( - eq(host1.memberIdentifier), eq(randomId), eq(PageRequest.of(0, 10)), eq(Long.MAX_VALUE)); + eq(host1.memberIdentifier), eq(randomId), eq(PageRequest.of(0, 10)), + eq(Long.MAX_VALUE)); } @Test @@ -191,8 +188,7 @@ void getReservationByEventId4() throws Exception { @WithMockCurator1 void getReservationById1() throws Exception { // given, when - mockMvc.perform(get("/api/reservations/" + randomId)) - .andDo(print()) + mockMvc.perform(get("/api/reservations/" + randomId)).andDo(print()) .andExpect(status().isOk()); // then @@ -204,7 +200,8 @@ void getReservationById1() throws Exception { @WithMockMember1 void createReservation1() throws Exception { // given - ArgumentCaptor captor = ArgumentCaptor.forClass(ReservationRequest.class); + ArgumentCaptor captor = ArgumentCaptor.forClass( + ReservationRequest.class); ReservationRequest request = ReservationData.reservation1.request.get(); // when @@ -225,7 +222,8 @@ void createReservation1() throws Exception { @WithMockCurator1 void createReservation2() throws Exception { // given - ArgumentCaptor captor = ArgumentCaptor.forClass(ReservationRequest.class); + ArgumentCaptor captor = ArgumentCaptor.forClass( + ReservationRequest.class); ReservationRequest request = ReservationData.reservation1.request.get(); // when @@ -350,7 +348,7 @@ void deleteReservation2() throws Exception { } @Test - @DisplayName("Host 권한으로 예약 ID를 통해 예약을 삭제한다.") + @DisplayName("Host 권한으로 예약 ID를 통해 예약을 삭제할 수 없다.") @WithMockHost1 void deleteReservation3() throws Exception { // given, when diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/review/ReviewControllerUnitTest.java b/src/test/java/org/ktc2/cokaen/wouldyouin/review/ReviewControllerUnitTest.java new file mode 100644 index 00000000..d7fcf7e8 --- /dev/null +++ b/src/test/java/org/ktc2/cokaen/wouldyouin/review/ReviewControllerUnitTest.java @@ -0,0 +1,571 @@ +package org.ktc2.cokaen.wouldyouin.review; + +import static java.lang.Math.abs; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.times; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.Random; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.ktc2.cokaen.wouldyouin._global.mockMember.WithMockCurator1; +import org.ktc2.cokaen.wouldyouin._global.mockMember.WithMockHost1; +import org.ktc2.cokaen.wouldyouin._global.mockMember.WithMockMember1; +import org.ktc2.cokaen.wouldyouin._global.testdata.MemberData.R.curator1; +import org.ktc2.cokaen.wouldyouin._global.testdata.MemberData.R.normal1; +import org.ktc2.cokaen.wouldyouin._global.testdata.ReviewData; +import org.ktc2.cokaen.wouldyouin.auth.application.JwtAuthFilter; +import org.ktc2.cokaen.wouldyouin.review.api.ReviewController; +import org.ktc2.cokaen.wouldyouin.review.api.dto.ReviewCreateRequest; +import org.ktc2.cokaen.wouldyouin.review.api.dto.ReviewEditRequest; +import org.ktc2.cokaen.wouldyouin.review.application.ReviewService; +import org.mockito.ArgumentCaptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.domain.PageRequest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +@WebMvcTest(ReviewController.class) +public class ReviewControllerUnitTest { + + private static final long randomId = abs(new Random().nextLong()); + @Autowired + private ObjectMapper objectMapper; + @MockBean + private ReviewService reviewService; + @MockBean + private JwtAuthFilter jwtAuthFilter; + @Autowired + private MockMvc mockMvc; + @Autowired + private WebApplicationContext context; + + @BeforeEach + public void setup() throws Exception { + mockMvc = MockMvcBuilders + .webAppContextSetup(context) + .apply(springSecurity()) + .build(); + } + + @Test + @DisplayName("jwt 토큰 정보에 해당하는 member의 리뷰 목록을 조회한다.") + @WithMockMember1 + void getReviewsByMemberId1() throws Exception { + // given, when + mockMvc.perform(get("/api/reviews") + .param("page", "5") + .param("size", "20") + .param("lastId", "100")).andDo(print()) + .andExpect(status().isOk()); + + // then + then(reviewService).should(times(1)).getAllByMemberId( + eq(normal1.id), eq(PageRequest.of(5, 20)), eq(100L)); + } + + @Test + @DisplayName("RequestParam으로 페이지 정보가 주어지지 않으면 기본값으로 리뷰 목록을 조회한다.") + @WithMockMember1 + void getReviewsByMemberId2() throws Exception { + // given, when + mockMvc.perform(get("/api/reviews")).andDo(print()) + .andExpect(status().isOk()); + + // then + then(reviewService).should(times(1)).getAllByMemberId( + eq(normal1.id), eq(PageRequest.of(0, 10)), eq(Long.MAX_VALUE)); + } + + @Test + @DisplayName("PathVariable로 이벤트 ID를 받아 해당하는 리뷰 목록을 조회한다.") + @WithMockMember1 + void getReviewsByEventId() throws Exception { + // given, when + mockMvc.perform(get("/api/reviews/events/" + randomId) + .param("page", "5") + .param("size", "20") + .param("lastId", "100")).andDo(print()) + .andExpect(status().isOk()); + + // then + then(reviewService).should(times(1)).getAllByEventId( + eq(randomId), eq(PageRequest.of(5, 20)), eq(100L)); + } + + @Test + @DisplayName("RequestParam으로 페이지 정보가 주어지지 않으면 기본값으로 리뷰 목록을 조회한다.") + @WithMockMember1 + void getReviewsByEventId2() throws Exception { + // given, when + mockMvc.perform(get("/api/reviews/events/" + randomId)) + .andDo(print()) + .andExpect(status().isOk()); + + // then + then(reviewService).should(times(1)).getAllByEventId( + eq(randomId), eq(PageRequest.of(0, 10)), eq(Long.MAX_VALUE)); + } + + @Test + @DisplayName("리뷰 ID를 통해 리뷰를 조회한다.") + @WithMockMember1 + void getReviewByReviewId() throws Exception { + // given, when + mockMvc.perform(get("/api/reviews/" + randomId)).andDo(print()) + .andExpect(status().isOk()); + + // then + then(reviewService).should(times(1)).getById(randomId); + } + + @Test + @DisplayName("jwt 토큰 정보에 해당하는 member가 리뷰를 작성하지 않은 이벤트 목록을 조회한다.") + @WithMockMember1 + void getUnreviewedEventsByMemberId1() throws Exception { + // given, when + mockMvc.perform(get("/api/reviews/events") + .param("page", "5") + .param("size", "20") + .param("lastId", "100")).andDo(print()) + .andExpect(status().isOk()); + + // then + then(reviewService).should(times(1)).getUnreviewedEventsByMemberId( + eq(normal1.id), eq(PageRequest.of(5, 20)), eq(100L)); + } + + @Test + @DisplayName("Host의 권한으로 자신이 리뷰를 작성하지 않은 이벤트 목록을 조회할 수 없다.") + @WithMockHost1 + void getUnreviewedEventsByMemberId2() throws Exception { + // given, when + mockMvc.perform(get("/api/reviews/events") + .param("page", "5") + .param("size", "20") + .param("lastId", "100")).andDo(print()) + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.message").value("요구된 멤버 형식과 실제 형식이 다릅니다.")); + + // then + then(reviewService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("Member 권한으로 RequestBody를 통해 리뷰를 생성한다.") + @WithMockMember1 + void createReview1() throws Exception { + // given + ArgumentCaptor captor = ArgumentCaptor.forClass( + ReviewCreateRequest.class); + ReviewCreateRequest request = ReviewData.review1.request.create.get(); + + // when + mockMvc.perform(post("/api/reviews") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isCreated()); + + // then + then(reviewService).should(times(1)).create(eq(normal1.id), captor.capture()); + assertThat(captor.getValue()).isEqualTo(request); + } + + @Test + @DisplayName("Curator 권한으로 RequestBody를 통해 리뷰를 생성한다.") + @WithMockCurator1 + void createReview2() throws Exception { + // given + ArgumentCaptor captor = ArgumentCaptor.forClass( + ReviewCreateRequest.class); + ReviewCreateRequest request = ReviewData.review1.request.create.get(); + + // when + mockMvc.perform(post("/api/reviews") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isCreated()); + + // then + then(reviewService).should(times(1)).create(eq(curator1.id), captor.capture()); + assertThat(captor.getValue()).isEqualTo(request); + } + + @Test + @DisplayName("Host 권한으로 RequestBody를 통해 리뷰를 생성할 수 없다.") + @WithMockHost1 + void createReview3() throws Exception { + // given + ArgumentCaptor captor = ArgumentCaptor.forClass( + ReviewCreateRequest.class); + ReviewCreateRequest request = ReviewData.review1.request.create.get(); + + // when + mockMvc.perform(post("/api/reviews") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.message").value("요구된 멤버 형식과 실제 형식이 다릅니다.")); + + // then + then(reviewService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("리뷰 생성 시 이벤트 ID는 빈 값이 될 수 없다.") + @WithMockMember1 + void createReview4() throws Exception { + // given + ReviewCreateRequest request = ReviewData.review1.request.create.get().toBuilder() + .eventId(null).build(); + + // when + mockMvc.perform(post("/api/reviews") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("이벤트 ID는 필수입니다.")); + + // then + then(reviewService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("리뷰 생성 시 별점은 빈 값이 될 수 없다.") + @WithMockMember1 + void createReview5() throws Exception { + // given + ReviewCreateRequest request = ReviewData.review1.request.create.get().toBuilder() + .score(null).build(); + + // when + mockMvc.perform(post("/api/reviews") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("별점은 필수입니다.")); + + // then + then(reviewService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("리뷰 생성 시 별점은 0점 이상이어야 한다.") + @WithMockMember1 + void createReview6() throws Exception { + // given + ReviewCreateRequest request = ReviewData.review1.request.create.get().toBuilder() + .score(-1).build(); + + // when + mockMvc.perform(post("/api/reviews") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("별점은 0점 이상입니다.")); + + // then + then(reviewService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("리뷰 생성 시 별점은 5점 이하이어야 한다.") + @WithMockMember1 + void createReview7() throws Exception { + // given + ReviewCreateRequest request = ReviewData.review1.request.create.get().toBuilder() + .score(8).build(); + + // when + mockMvc.perform(post("/api/reviews") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("별점은 5점 이하입니다.")); + + // then + then(reviewService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("리뷰 생성 시 내용은 빈 값이 될 수 없다.") + @WithMockMember1 + void createReview8() throws Exception { + // given + ReviewCreateRequest request = ReviewData.review1.request.create.get().toBuilder() + .content(null).build(); + + // when + mockMvc.perform(post("/api/reviews") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("내용은 필수입니다.")); + + // then + then(reviewService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("리뷰 생성 시 내용은 5자 이상 50자 이하입니다.") + @WithMockMember1 + void createReview9() throws Exception { + // given + ReviewCreateRequest request = ReviewData.review1.request.create.get().toBuilder() + .content("짧은내용").build(); + + // when + mockMvc.perform(post("/api/reviews") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("내용은 5자 이상 50자 이하입니다.")); + + // then + then(reviewService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("Member 권한으로 RequestBody를 통해 리뷰를 수정한다.") + @WithMockMember1 + void updateReview1() throws Exception { + // given + ArgumentCaptor captor = ArgumentCaptor.forClass( + ReviewEditRequest.class); + ReviewEditRequest request = ReviewData.review1.request.edit1.get(); + + // when + mockMvc.perform(put("/api/reviews/" + randomId) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isOk()); + + // then + then(reviewService).should(times(1)).update(eq(normal1.id), eq(randomId), captor.capture()); + assertThat(captor.getValue()).isEqualTo(request); + } + + @Test + @DisplayName("Curator 권한으로 RequestBody를 통해 리뷰를 생성한다.") + @WithMockCurator1 + void updateReview2() throws Exception { + // given + ArgumentCaptor captor = ArgumentCaptor.forClass( + ReviewEditRequest.class); + ReviewEditRequest request = ReviewData.review1.request.edit1.get(); + + // when + mockMvc.perform(put("/api/reviews/" + randomId) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isOk()); + + // then + then(reviewService).should(times(1)) + .update(eq(curator1.id), eq(randomId), captor.capture()); + assertThat(captor.getValue()).isEqualTo(request); + } + + @Test + @DisplayName("Host 권한으로 RequestBody를 통해 리뷰를 생성할 수 없다.") + @WithMockHost1 + void updateReview3() throws Exception { + // given, when + mockMvc.perform(put("/api/reviews/" + randomId) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(ReviewData.review1.request.edit1.get()))) + .andDo(print()) + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.message").value("요구된 멤버 형식과 실제 형식이 다릅니다.")); + + // then + then(reviewService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("리뷰 수정 시, 별점은 빈 값이 될 수 없다. ") + @WithMockCurator1 + void updateReview4() throws Exception { + // given + ReviewEditRequest request = ReviewData.review1.request.edit1.get().toBuilder() + .score(null).build(); + + // when + mockMvc.perform(put("/api/reviews/" + randomId) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("별점은 필수입니다.")); + + // then + then(reviewService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("리뷰 수정 시, 별점은 0점 이상이어야 한다.") + @WithMockCurator1 + void updateReview5() throws Exception { + // given + ReviewEditRequest request = ReviewData.review1.request.edit1.get().toBuilder() + .score(-1).build(); + + // when + mockMvc.perform(put("/api/reviews/" + randomId) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("별점은 0점 이상입니다.")); + + // then + then(reviewService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("리뷰 수정 시, 별점은 5점 이하여야 한다.") + @WithMockCurator1 + void updateReview6() throws Exception { + // given + ReviewEditRequest request = ReviewData.review1.request.edit1.get().toBuilder() + .score(8).build(); + + // when + mockMvc.perform(put("/api/reviews/" + randomId) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("별점은 5점 이하입니다.")); + + // then + then(reviewService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("리뷰 수정 시, 내용은 필수입니다.") + @WithMockCurator1 + void updateReview7() throws Exception { + // given + ReviewEditRequest request = ReviewData.review1.request.edit1.get().toBuilder() + .content(null).build(); + + // when + mockMvc.perform(put("/api/reviews/" + randomId) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("내용은 필수입니다.")); + + // then + then(reviewService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("리뷰 수정 시, 내용은 5자 이상 50자 이하입니다.") + @WithMockCurator1 + void updateReview8() throws Exception { + // given + ReviewEditRequest request = ReviewData.review1.request.edit1.get().toBuilder() + .content("짧은내용").build(); + + // when + mockMvc.perform(put("/api/reviews/" + randomId) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("내용은 5자 이상 50자 이하입니다.")); + + // then + then(reviewService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("Member 권한으로 리뷰 ID를 통해 리뷰를 삭제한다.") + @WithMockMember1 + void deleteReview1() throws Exception { + // given, when + mockMvc.perform(delete("/api/reviews/" + randomId) + .with(csrf())) + .andDo(print()) + .andExpect(status().isNoContent()); + + // then + then(reviewService).should(times(1)).delete(normal1.id, randomId); + } + + @Test + @DisplayName("Curator 권한으로 리뷰 ID를 통해 리뷰를 삭제한다.") + @WithMockCurator1 + void deleteReview2() throws Exception { + // given, when + mockMvc.perform(delete("/api/reviews/" + randomId) + .with(csrf())) + .andDo(print()) + .andExpect(status().isNoContent()); + + // then + then(reviewService).should(times(1)).delete(curator1.id, randomId); + } + + @Test + @DisplayName("Host 권한으로 리뷰 ID를 통해 리뷰를 삭제할 수 없다.") + @WithMockHost1 + void deleteReview3() throws Exception { + // given, when + mockMvc.perform(delete("/api/reviews/" + randomId) + .with(csrf())) + .andDo(print()) + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.message").value("요구된 멤버 형식과 실제 형식이 다릅니다.")); + + // then + then(reviewService).shouldHaveNoInteractions(); + } +}