Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,30 +24,49 @@ public class StampCommandService {
private final StampQueryRepository stampQueryRepository;

public Stamp createStamp(Trip trip, CreateStampRequest request) {
Stamp newStamp =
StampFactory.create(trip, request.name(), request.order(), request.endDate());

List<Stamp> existingStamps =
stampRepository.findAllByTripIdAndDeletedAtIsNull(trip.getId());
List<Stamp> combinedStamps = new ArrayList<>(existingStamps);
combinedStamps.add(newStamp);
int nextOrder = 0;
if (trip.getCategory() == TripCategory.COURSE) {
nextOrder = computeNextStampOrder(trip.getId());
}

StampPolicy.validateStampOrders(trip.getCategory(), combinedStamps);
StampPolicy.validateEndDate(trip.getEndDate(), newStamp.getEndDate());
Stamp stamp = StampFactory.create(trip, request.name(), nextOrder, request.endDate());
StampPolicy.validateEndDate(trip.getEndDate(), stamp.getEndDate());

return stampRepository.save(newStamp);
return stampRepository.save(stamp);
}

public void createStamps(Trip trip, List<CreateStampRequest> requests) {
List<Stamp> stamps =
requests.stream()
.map(
stamp ->
StampFactory.create(
trip, stamp.name(), stamp.order(), stamp.endDate()))
.toList();

StampPolicy.validateStampOrders(trip.getCategory(), stamps);
if (requests == null || requests.isEmpty()) return;

final List<Stamp> stamps =
switch (trip.getCategory()) {
// 탐험형 여행일 경우
// order 0 으로 전부 고정
case EXPLORE -> requests.stream()
.map(
stamp ->
StampFactory.create(
trip, stamp.name(), 0, stamp.endDate()))
.toList();

// 코스형 여행일 경우
// nextOrder 부터 1씩 증가하며 order 저장
case COURSE -> {
int nextOrder = computeNextStampOrder(trip.getId());

List<Stamp> stampList = new ArrayList<>();
for (CreateStampRequest request : requests) {
Stamp stamp =
StampFactory.create(
trip, request.name(), nextOrder++, request.endDate());
stampList.add(stamp);
}

yield stampList;
}
};

stamps.forEach(stamp -> StampPolicy.validateEndDate(trip.getEndDate(), stamp.getEndDate()));

stampRepository.saveAll(stamps);
}
Expand Down Expand Up @@ -151,4 +170,9 @@ public void decreaseTotalMissions(Stamp stamp) {
public void increaseCompletedMissions(Stamp stamp, int count) {
stamp.increaseCompletedMissions(count);
}

private int computeNextStampOrder(Long tripId) {
Integer lastOrder = stampQueryRepository.findMaxStampOrderByTripId(tripId);
return lastOrder == null ? 1 : lastOrder + 1;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@
import com.ject.studytrip.stamp.domain.model.Stamp;
import com.ject.studytrip.trip.domain.model.TripCategory;
import java.time.LocalDate;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;

Expand All @@ -23,30 +21,6 @@ public static void validateNotDeleted(Stamp stamp) {
throw new CustomException(StampErrorCode.STAMP_ALREADY_DELETED);
}

public static void validateStampOrders(TripCategory tripCategory, List<Stamp> stamps) {
int maxOrder = stamps.size();
Set<Integer> orderSet = new HashSet<>();

for (Stamp stamp : stamps) {
int order = stamp.getStampOrder();

// 탐험형 여행이면서 순서가 존재할 경우
if (tripCategory == TripCategory.EXPLORE && order > 0)
throw new CustomException(StampErrorCode.INVALID_STAMP_ORDER_FOR_EXPLORATION_TRIP);

if (tripCategory == TripCategory.COURSE) {
// 코스형 여행이면서 순서가 1보다 작거나 총 개수보다 큰 경우
if (order < 1 || order > maxOrder)
throw new CustomException(
StampErrorCode.INVALID_STAMP_ORDER_RANGE_FOR_COURSE_TRIP);

// 코스형 여행이면서 중복된 순서일 경우
if (!orderSet.add(order))
throw new CustomException(StampErrorCode.DUPLICATE_STAMP_ORDER_FOR_COURSE_TRIP);
}
}
}

public static void validateUpdateStampOrders(
TripCategory tripCategory, List<Long> orderedStampIds, List<Stamp> savedStamps) {
if (tripCategory == TripCategory.EXPLORE && !orderedStampIds.isEmpty())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@ public interface StampQueryRepository {
long deleteAllByDeletedAtIsNotNull();

long deleteAllByDeletedTripOwner();

Integer findMaxStampOrderByTripId(Long tripId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,13 @@ public long deleteAllByDeletedTripOwner() {
.where(trip.deletedAt.isNotNull())))
.execute();
}

@Override
public Integer findMaxStampOrderByTripId(Long tripId) {
return queryFactory
.select(stamp.stampOrder.max().coalesce(0))
.from(stamp)
.where(stamp.trip.id.eq(tripId), stamp.deletedAt.isNull())
.fetchOne();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,10 @@

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.FutureOrPresent;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotEmpty;
import java.time.LocalDate;

public record CreateStampRequest(
@Schema(description = "스탬프 이름") @NotEmpty(message = "스탬프 이름은 필수 요청 값입니다.") String name,
@Schema(description = "스탬프 순서") @Min(value = 0, message = "스탬프 순서는 최소 0 이상이여야 합니다.")
int order,
@Schema(description = "스탬프 종료일") @FutureOrPresent(message = "스탬프 종료일은 현재 날짜보다 과거일 수 없습니다.")
LocalDate endDate) {}
Original file line number Diff line number Diff line change
Expand Up @@ -63,46 +63,6 @@ void setup() {
class CreateStamp {
private final CreateStampRequestFixture fixture = new CreateStampRequestFixture();

@Test
@DisplayName("탐험형 여행에 순서가 지정된 스탬프를 등록하면 예외가 발생한다")
void shouldThrowExceptionWhenOrderSpecifiedForExploreTrip() {
// given
CreateStampRequest request = fixture.withStampOrder(1).build();

// when & then
assertThatThrownBy(() -> stampCommandService.createStamp(exploreTrip, request))
.isInstanceOf(CustomException.class)
.hasMessage(
StampErrorCode.INVALID_STAMP_ORDER_FOR_EXPLORATION_TRIP.getMessage());
}

@Test
@DisplayName("코스형 여행에 순서가 유효 범위를 벗어난 경우 예외가 발생한다")
void shouldThrowExceptionWhenOrderOutOfRange() {
// given
CreateStampRequest request = fixture.withStampOrder(1000).build();

// when & then
assertThatThrownBy(() -> stampCommandService.createStamp(courseTrip, request))
.isInstanceOf(CustomException.class)
.hasMessage(
StampErrorCode.INVALID_STAMP_ORDER_RANGE_FOR_COURSE_TRIP.getMessage());
}

@Test
@DisplayName("코스형 여행에 중복된 순서의 스탬프를 등록하면 예외가 발생한다")
void shouldThrowExceptionWhenDuplicateOrderForCourseTrip() {
// given
CreateStampRequest request = fixture.withStampOrder(1).build();
given(stampRepository.findAllByTripIdAndDeletedAtIsNull(courseTrip.getId()))
.willReturn(List.of(courseStamp1));

// when & then
assertThatThrownBy(() -> stampCommandService.createStamp(courseTrip, request))
.isInstanceOf(CustomException.class)
.hasMessage(StampErrorCode.DUPLICATE_STAMP_ORDER_FOR_COURSE_TRIP.getMessage());
}

@Test
@DisplayName("스탬프 종료일이 과거 날짜라면 예외가 발생한다")
void shouldThrowExceptionWhenEndDateIsInPast() {
Expand Down Expand Up @@ -130,87 +90,85 @@ void shouldThrowExceptionWhenEndDateIsAfterTripEndDate() {
}

@Test
@DisplayName("유효한 요청으로 스탬프를 생성하면 스탬프가 저장되고 반환된다")
void shouldCreateValidStamp() {
@DisplayName("탐험형 여행에서는 order가 항상 0으로 저장된다")
void shouldCreateExploreStampWithOrderZero() {
// given
CreateStampRequest request = fixture.build();
Stamp saved = Stamp.of(courseTrip, request.name(), request.order(), request.endDate());
// exploreTrip은 order 고정(0), save 응답 스텁
Stamp saved = Stamp.of(exploreTrip, request.name(), 0, request.endDate());
given(stampRepository.save(any())).willReturn(saved);

// when
Stamp stamp = stampCommandService.createStamp(courseTrip, request);
Stamp stamp = stampCommandService.createStamp(exploreTrip, request);

// then
assertThat(stamp.getName()).isEqualTo(saved.getName());
assertThat(stamp.getStampOrder()).isEqualTo(saved.getStampOrder());
assertThat(stamp.getStampOrder()).isEqualTo(0);
}
}

@Nested
@DisplayName("createStamps 메서드는")
class CreateStamps {
private final CreateStampRequestFixture fixture = new CreateStampRequestFixture();

@Test
@DisplayName("탐험형 여행에서 순서가 1 이상이면 예외가 발생한다")
void shouldThrowExceptionWhenOrderExistsInExploreTrip() {
@DisplayName("코스형 여행에서는 마지막 order 다음 값으로 저장된다")
void shouldCreateCourseStampWithNextSequentialOrder() {
// given
List<CreateStampRequest> requests = List.of(fixture.withStampOrder(1).build());
CreateStampRequest request = fixture.build();

// when & then
assertThatThrownBy(() -> stampCommandService.createStamps(exploreTrip, requests))
.isInstanceOf(CustomException.class)
.hasMessage(
StampErrorCode.INVALID_STAMP_ORDER_FOR_EXPLORATION_TRIP.getMessage());
}
// 마지막 스탬프 순서가 2라고 가정, 신규는 3으로 저장
given(stampQueryRepository.findMaxStampOrderByTripId(courseTrip.getId())).willReturn(2);
Stamp saved = Stamp.of(courseTrip, request.name(), 3, request.endDate());
given(stampRepository.save(any())).willReturn(saved);

@Test
@DisplayName("코스형 여행에서 순서가 1 미만 또는 총 개수 초과라면 예외가 발생한다")
void shouldThrowExceptionWhenStampOrderIsOutOfRangeForCourseTrip() {
// given
List<CreateStampRequest> requests = List.of(fixture.withStampOrder(2).build());
// when
Stamp stamp = stampCommandService.createStamp(courseTrip, request);

// when & then
assertThatThrownBy(() -> stampCommandService.createStamps(courseTrip, requests))
.isInstanceOf(CustomException.class)
.hasMessage(
StampErrorCode.INVALID_STAMP_ORDER_RANGE_FOR_COURSE_TRIP.getMessage());
// then
assertThat(stamp.getStampOrder()).isEqualTo(3);
}

@Test
@DisplayName("코스형 여행에서 순서가 중복되면 예외가 발생한다")
void shouldThrowExceptionWhenDuplicateOrderInCourseTrip() {
@DisplayName("유효한 요청으로 스탬프를 생성하면 저장되고 반환된다")
void shouldCreateValidStamp() {
// given
List<CreateStampRequest> requests =
List.of(fixture.withStampOrder(1).build(), fixture.withStampOrder(1).build());
CreateStampRequest request = fixture.build();
given(stampQueryRepository.findMaxStampOrderByTripId(courseTrip.getId()))
.willReturn(null);
Stamp saved = Stamp.of(courseTrip, request.name(), 1, request.endDate());
given(stampRepository.save(any())).willReturn(saved);

// when & then
assertThatThrownBy(() -> stampCommandService.createStamps(courseTrip, requests))
.isInstanceOf(CustomException.class)
.hasMessage(StampErrorCode.DUPLICATE_STAMP_ORDER_FOR_COURSE_TRIP.getMessage());
// when
Stamp stamp = stampCommandService.createStamp(courseTrip, request);

// then
assertThat(stamp.getName()).isEqualTo(saved.getName());
assertThat(stamp.getStampOrder()).isEqualTo(1);
}
}

@Nested
@DisplayName("createStamps 메서드는")
class CreateStamps {
private final CreateStampRequestFixture fixture = new CreateStampRequestFixture();

@Test
@DisplayName("코스형 여행의 유효한 스탬프 리스트를 넘기면 저장된다")
void shouldCreateStampsForCourseTrip() {
@DisplayName("탐험형 여행에서는 전달한 모든 스탬프의 order가 0으로 저장된다")
void shouldCreateExploreStampsWithOrderZero() {
// given
List<CreateStampRequest> requests = List.of(fixture.build());
List<CreateStampRequest> requests = List.of(fixture.build(), fixture.build());

// when
stampCommandService.createStamps(courseTrip, requests);
stampCommandService.createStamps(exploreTrip, requests);

// then
verify(stampRepository).saveAll(anyList());
}

@Test
@DisplayName("탐험형 여행의 유효한 스탬프 리스트를 넘기면 저장된다")
void shouldCreateStampsForExploreTrip() {
@DisplayName("코스형 여행에서는 마지막 order 다음 값부터 순차적으로 저장된다")
void shouldCreateCourseStampsSequentiallyFromNextOrder() {
// given
List<CreateStampRequest> requests = List.of(fixture.withStampOrder(0).build());
List<CreateStampRequest> requests = List.of(fixture.build(), fixture.build());
given(stampQueryRepository.findMaxStampOrderByTripId(courseTrip.getId())).willReturn(5);

// when
stampCommandService.createStamps(exploreTrip, requests);
stampCommandService.createStamps(courseTrip, requests);

// then
verify(stampRepository).saveAll(anyList());
Expand All @@ -225,6 +183,7 @@ class UpdateStamp {
@Test
@DisplayName("유효한 정보로 스탬프의 이름을 수정하면 스탬프가 업데이트된다")
void shouldUpdateStampName() {

// given
UpdateStampRequest request = fixture.buildUpdateName();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,13 @@
public class CreateStampRequestFixture {

private String name = "TEST STAMP";
private int stampOrder = 1;
private LocalDate endDate = LocalDate.now().plusDays(7);
private java.time.LocalDate endDate = java.time.LocalDate.now().plusDays(7);

public CreateStampRequestFixture withName(String name) {
this.name = name;
return this;
}

public CreateStampRequestFixture withStampOrder(int stampOrder) {
this.stampOrder = stampOrder;
return this;
}

public CreateStampRequestFixture withEndDateInPast() {
this.endDate = LocalDate.now().minusDays(1);
return this;
Expand All @@ -30,6 +24,6 @@ public CreateStampRequestFixture withEndDateAfterTripEndDate() {
}

public CreateStampRequest build() {
return new CreateStampRequest(name, stampOrder, endDate);
return new CreateStampRequest(name, endDate);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
import com.ject.studytrip.stamp.domain.factory.StampFactory;
import com.ject.studytrip.stamp.domain.model.Stamp;
import com.ject.studytrip.trip.domain.model.Trip;
import java.time.LocalDate;
import org.springframework.test.util.ReflectionTestUtils;

public class StampFixture {
private static final String STAMP_NAME = "TEST STAMP NAME";
private static final LocalDate DEFAULT_END_DATE = LocalDate.now().plusDays(7);
private static final java.time.LocalDate DEFAULT_END_DATE =
java.time.LocalDate.now().plusDays(7);

public static Stamp createStamp(Trip trip, int order) {
return StampFactory.create(trip, STAMP_NAME, order, DEFAULT_END_DATE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

public class UpdateStampRequestFixture {
private String name = "TEST STAMP";
private LocalDate endDate = LocalDate.now().plusDays(7);
private java.time.LocalDate endDate = java.time.LocalDate.now().plusDays(7);

public UpdateStampRequestFixture withName(String name) {
this.name = name;
Expand Down
Loading