diff --git a/server/src/intTest/java/com/ticketing/server/movie/aop/TicketLockAspectTest.java b/server/src/intTest/java/com/ticketing/server/movie/aop/TicketLockAspectTest.java new file mode 100644 index 0000000..cdac364 --- /dev/null +++ b/server/src/intTest/java/com/ticketing/server/movie/aop/TicketLockAspectTest.java @@ -0,0 +1,56 @@ +package com.ticketing.server.movie.aop; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class TicketLockAspectTest { + + @Autowired + private TicketLockAspect ticketLockAspect; + + @Test + @DisplayName("티켓 lock 동시성 체크") + @SuppressWarnings({"java:S5960"}) + void ticketMultiThread() throws InterruptedException { + // given + ExecutorService executorService = Executors.newFixedThreadPool(2); + CountDownLatch latch = new CountDownLatch(2); + + List lockIds = List.of("TicketLock:1", "TicketLock:2", "TicketLock:3"); + AtomicBoolean result1 = new AtomicBoolean(Boolean.TRUE); + AtomicBoolean result2 = new AtomicBoolean(Boolean.TRUE); + + // when + executorService.execute(() -> { + result1.set(ticketLockAspect.isEveryTicketIdLock(lockIds)); + latch.countDown(); + }); + + executorService.execute(() -> { + result2.set(ticketLockAspect.isEveryTicketIdLock(List.of("TicketLock:1"))); + latch.countDown(); + }); + + latch.await(); + + // then + Long unlockCount = ticketLockAspect.ticketIdsUnlock(lockIds); + + assertAll( + () -> assertThat(result1).isNotEqualTo(result2), + () -> assertThat(unlockCount > 1).isTrue() + ); + } + +} diff --git a/server/src/main/java/com/ticketing/server/global/exception/ErrorCode.java b/server/src/main/java/com/ticketing/server/global/exception/ErrorCode.java index cff84fb..689e66d 100644 --- a/server/src/main/java/com/ticketing/server/global/exception/ErrorCode.java +++ b/server/src/main/java/com/ticketing/server/global/exception/ErrorCode.java @@ -5,7 +5,6 @@ import static org.springframework.http.HttpStatus.CONFLICT; import static org.springframework.http.HttpStatus.FORBIDDEN; import static org.springframework.http.HttpStatus.NOT_FOUND; -import com.ticketing.server.global.redis.PaymentCache; import lombok.AllArgsConstructor; import lombok.Getter; import org.springframework.http.HttpStatus; @@ -24,8 +23,11 @@ public enum ErrorCode { BAD_REQUEST_PAYMENT_COMPLETE(BAD_REQUEST, "처리할 결제 정보가 존재하지 않습니다."), BAD_REQUEST_PAYMENT_READY(BAD_REQUEST, "이미 진행 중인 결제가 존재합니다."), BAD_REQUEST_PAYMENT_CANCEL(BAD_REQUEST, "취소할 티켓이 존재하지 않습니다."), + BAD_REQUEST_TICKET_RESERVATION(BAD_REQUEST, "이미 다른 고객이 예약 진행 중인 좌석이 존재합니다."), + BAD_REQUEST_TICKET_SOLD(BAD_REQUEST, "이미 환불 진행 중 입니다."), NOT_REFUNDABLE_TIME(BAD_REQUEST, "환불이 가능한 시간이 지났습니다."), NOT_REFUNDABLE_SEAT(BAD_REQUEST, "환불할 수 있는 좌석이 아닙니다."), + EMPTY_TICKET_ID(BAD_REQUEST, "티켓 정보가 존재하지 않습니다."), /* 403 FORBIDDEN : 접근 권한 제한 */ VALID_USER_ID(FORBIDDEN, "해당 정보에 접근 권한이 존재하지 않습니다."), @@ -48,7 +50,7 @@ public enum ErrorCode { DELETED_MOVIE(CONFLICT, "이미 삭제된 영화 입니다."); private final HttpStatus httpStatus; - private String detail; + private final String detail; /* 400 BAD_REQUEST : 잘못된 요청 */ public static TicketingException throwMismatchPassword() { @@ -67,14 +69,6 @@ public enum ErrorCode { throw new TicketingException(UNABLE_CHANGE_GRADE); } - public static TicketingException throwInvalidTicketId() { - throw new TicketingException(INVALID_TICKET_ID); - } - - public static TicketingException throwBadRequestMovieTime() { - throw new TicketingException(BAD_REQUEST_MOVIE_TIME); - } - public static TicketingException throwBadRequestPaymentComplete() { throw new TicketingException(BAD_REQUEST_PAYMENT_COMPLETE); } @@ -83,27 +77,12 @@ public enum ErrorCode { throw new TicketingException(BAD_REQUEST_PAYMENT_READY); } - public static TicketingException throwBadRequestPaymentCancel() { - throw new TicketingException(BAD_REQUEST_PAYMENT_CANCEL); - } - - public static TicketingException throwNotRefundableTime() { - throw new TicketingException(NOT_REFUNDABLE_TIME); - } - - public static TicketingException throwNotRefundableSeat() { - throw new TicketingException(NOT_REFUNDABLE_SEAT); - } - /* 403 FORBIDDEN : 접근 권한 제한 */ public static TicketingException throwValidUserId() { throw new TicketingException(VALID_USER_ID); } /* 404 NOT_FOUND : Resource 를 찾을 수 없음 */ - public static TicketingException throwUserNotFound() { - throw new TicketingException(USER_NOT_FOUND); - } public static TicketingException throwEmailNotFound() { throw new TicketingException(EMAIL_NOT_FOUND); @@ -113,10 +92,6 @@ public enum ErrorCode { throw new TicketingException(MOVIE_NOT_FOUND); } - public static TicketingException throwMovieTimeNotFound() { - throw new TicketingException(MOVIE_TIME_NOT_FOUND); - } - public static TicketingException throwRefreshTokenNotFound() { throw new TicketingException(REFRESH_TOKEN_NOT_FOUND); } diff --git a/server/src/main/java/com/ticketing/server/movie/aop/TicketLockAspect.java b/server/src/main/java/com/ticketing/server/movie/aop/TicketLockAspect.java new file mode 100644 index 0000000..c048756 --- /dev/null +++ b/server/src/main/java/com/ticketing/server/movie/aop/TicketLockAspect.java @@ -0,0 +1,64 @@ +package com.ticketing.server.movie.aop; + +import static com.ticketing.server.movie.domain.TicketLock.LOCK_VALUE; + +import com.ticketing.server.global.exception.ErrorCode; +import com.ticketing.server.global.exception.TicketingException; +import com.ticketing.server.movie.service.dto.TicketIdsDTO; +import java.util.List; +import java.util.concurrent.TimeUnit; +import lombok.RequiredArgsConstructor; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +@Component +@Aspect +@RequiredArgsConstructor +public class TicketLockAspect { + + private final RedisTemplate redisTemplate; + + @Around("execution(* com.ticketing.server.movie.service.TicketLockService.*(..))") + public Object ticketLock(ProceedingJoinPoint joinPoint) throws Throwable { + List ticketLockIds = getTicketLockIds(joinPoint); + + try { + if (!isEveryTicketIdLock(ticketLockIds)) { + throw new TicketingException(ErrorCode.BAD_REQUEST_TICKET_SOLD); + } + + return joinPoint.proceed(); + } finally { + ticketIdsUnlock(ticketLockIds); + } + } + + protected boolean isEveryTicketIdLock(List ids) { + for (String id : ids) { + if (Boolean.FALSE.equals(redisTemplate.opsForValue().setIfAbsent(id, LOCK_VALUE.getValue(), 5, TimeUnit.MINUTES))) { + return Boolean.FALSE; + } + } + return Boolean.TRUE; + } + + protected Long ticketIdsUnlock(List ids) { + return redisTemplate.delete(ids); + } + + private List getTicketLockIds(ProceedingJoinPoint joinPoint) { + for (Object arg : joinPoint.getArgs()) { + if (arg instanceof TicketIdsDTO) { + TicketIdsDTO ids = (TicketIdsDTO) arg; + return ids.makeTicketLockIds(); + } + } + + throw new TicketingException(ErrorCode.EMPTY_TICKET_ID); + } + + +} diff --git a/server/src/main/java/com/ticketing/server/movie/domain/Ticket.java b/server/src/main/java/com/ticketing/server/movie/domain/Ticket.java index 8f12867..16ed074 100644 --- a/server/src/main/java/com/ticketing/server/movie/domain/Ticket.java +++ b/server/src/main/java/com/ticketing/server/movie/domain/Ticket.java @@ -1,7 +1,13 @@ package com.ticketing.server.movie.domain; +import static com.ticketing.server.global.exception.ErrorCode.BAD_REQUEST_PAYMENT_CANCEL; +import static com.ticketing.server.global.exception.ErrorCode.DUPLICATE_PAYMENT; +import static com.ticketing.server.global.exception.ErrorCode.NOT_REFUNDABLE_SEAT; +import static com.ticketing.server.global.exception.ErrorCode.NOT_REFUNDABLE_TIME; + import com.ticketing.server.global.dto.repository.AbstractEntity; import com.ticketing.server.global.exception.ErrorCode; +import com.ticketing.server.global.exception.TicketingException; import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; import javax.persistence.Entity; @@ -56,7 +62,7 @@ public class Ticket extends AbstractEntity { public Ticket makeReservation() { if (!TicketStatus.SALE.equals(status)) { - throw ErrorCode.throwDuplicatePayment(); + throw new TicketingException(DUPLICATE_PAYMENT); } status = TicketStatus.RESERVATION; @@ -65,7 +71,7 @@ public class Ticket extends AbstractEntity { public Ticket makeSold(Long paymentId) { if (TicketStatus.SOLD.equals(status)) { - throw ErrorCode.throwDuplicatePayment(); + throw new TicketingException(DUPLICATE_PAYMENT); } status = TicketStatus.SOLD; @@ -75,7 +81,7 @@ public class Ticket extends AbstractEntity { public Ticket cancel() { if (!TicketStatus.RESERVATION.equals(status)) { - throw ErrorCode.throwBadRequestPaymentCancel(); + throw new TicketingException(BAD_REQUEST_PAYMENT_CANCEL); } status = TicketStatus.SALE; @@ -86,7 +92,7 @@ public class Ticket extends AbstractEntity { public Ticket refund(LocalDateTime dateTime) { long seconds = ChronoUnit.SECONDS.between(dateTime, getStartAt()); if (600L > seconds) { - throw ErrorCode.throwNotRefundableTime(); + throw new TicketingException(NOT_REFUNDABLE_TIME); } return refund(); @@ -94,7 +100,7 @@ public class Ticket extends AbstractEntity { public Ticket refund() { if (!TicketStatus.SOLD.equals(status)) { - throw ErrorCode.throwNotRefundableSeat(); + throw new TicketingException(NOT_REFUNDABLE_SEAT); } status = TicketStatus.SALE; diff --git a/server/src/main/java/com/ticketing/server/movie/domain/TicketLock.java b/server/src/main/java/com/ticketing/server/movie/domain/TicketLock.java new file mode 100644 index 0000000..4bd3eaf --- /dev/null +++ b/server/src/main/java/com/ticketing/server/movie/domain/TicketLock.java @@ -0,0 +1,15 @@ +package com.ticketing.server.movie.domain; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum TicketLock { + + LOCK_KEY("TicketLock"), + LOCK_VALUE("lock"); + + private final String value; + +} diff --git a/server/src/main/java/com/ticketing/server/movie/service/TicketLockService.java b/server/src/main/java/com/ticketing/server/movie/service/TicketLockService.java new file mode 100644 index 0000000..412f93d --- /dev/null +++ b/server/src/main/java/com/ticketing/server/movie/service/TicketLockService.java @@ -0,0 +1,77 @@ +package com.ticketing.server.movie.service; + +import static com.ticketing.server.global.exception.ErrorCode.BAD_REQUEST_MOVIE_TIME; +import static com.ticketing.server.global.exception.ErrorCode.INVALID_TICKET_ID; + +import com.ticketing.server.global.exception.TicketingException; +import com.ticketing.server.movie.domain.Ticket; +import com.ticketing.server.movie.domain.repository.TicketRepository; +import com.ticketing.server.movie.service.dto.TicketIdsDTO; +import com.ticketing.server.movie.service.dto.TicketReservationDTO; +import com.ticketing.server.movie.service.dto.TicketSoldDTO; +import com.ticketing.server.movie.service.dto.TicketsReservationDTO; +import com.ticketing.server.movie.service.dto.TicketsSoldDTO; +import java.util.List; +import java.util.stream.Collectors; +import javax.validation.Valid; +import javax.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +@Service +@RequiredArgsConstructor +@Validated +public class TicketLockService { + + private final TicketRepository ticketRepository; + + public TicketsReservationDTO ticketReservation(@Valid TicketIdsDTO ticketIdsDto) { + List tickets = getTicketsByInTicketIds(ticketIdsDto.getTicketIds()); + + Long firstMovieTimeId = firstMovieTimeId(tickets); + List reservationDtoList = tickets.stream() + .map(Ticket::makeReservation) + .filter(ticket -> firstMovieTimeId.equals(ticket.getMovieTimeId())) + .map(TicketReservationDTO::new) + .collect(Collectors.toList()); + + if (tickets.size() != reservationDtoList.size()) { + throw new TicketingException(BAD_REQUEST_MOVIE_TIME); + } + + return new TicketsReservationDTO(firstMovieTitle(tickets), reservationDtoList); + } + + public TicketsSoldDTO ticketSold(@NotNull Long paymentId, @Valid TicketIdsDTO ticketIdsDto) { + List tickets = getTicketsByInTicketIds(ticketIdsDto.getTicketIds()); + + List soldDtoList = tickets.stream() + .map(ticket -> ticket.makeSold(paymentId)) + .map(TicketSoldDTO::new) + .collect(Collectors.toList()); + + return new TicketsSoldDTO(paymentId, soldDtoList); + } + + private List getTicketsByInTicketIds(List ticketIds) { + List tickets = ticketRepository.findTicketFetchJoinByTicketIds(ticketIds); + + if (tickets.size() != ticketIds.size()) { + throw new TicketingException(INVALID_TICKET_ID); + } + + return tickets; + } + + private Long firstMovieTimeId(List tickets) { + Ticket ticket = tickets.get(0); + return ticket.getMovieTimeId(); + } + + private String firstMovieTitle(List tickets) { + Ticket ticket = tickets.get(0); + return ticket.getMovieTitle(); + } + +} diff --git a/server/src/main/java/com/ticketing/server/movie/service/TicketServiceImpl.java b/server/src/main/java/com/ticketing/server/movie/service/TicketServiceImpl.java index 15a5ef7..bb48dbd 100644 --- a/server/src/main/java/com/ticketing/server/movie/service/TicketServiceImpl.java +++ b/server/src/main/java/com/ticketing/server/movie/service/TicketServiceImpl.java @@ -1,15 +1,18 @@ package com.ticketing.server.movie.service; -import com.ticketing.server.global.exception.ErrorCode; +import static com.ticketing.server.global.exception.ErrorCode.INVALID_TICKET_ID; +import static com.ticketing.server.global.exception.ErrorCode.MOVIE_TIME_NOT_FOUND; +import static com.ticketing.server.global.exception.ErrorCode.PAYMENT_ID_NOT_FOUND; + +import com.ticketing.server.global.exception.TicketingException; import com.ticketing.server.global.validator.constraints.NotEmptyCollection; import com.ticketing.server.movie.domain.MovieTime; import com.ticketing.server.movie.domain.Ticket; import com.ticketing.server.movie.domain.repository.MovieTimeRepository; import com.ticketing.server.movie.domain.repository.TicketRepository; import com.ticketing.server.movie.service.dto.TicketDTO; +import com.ticketing.server.movie.service.dto.TicketIdsDTO; import com.ticketing.server.movie.service.dto.TicketRefundDTO; -import com.ticketing.server.movie.service.dto.TicketReservationDTO; -import com.ticketing.server.movie.service.dto.TicketSoldDTO; import com.ticketing.server.movie.service.dto.TicketsCancelDTO; import com.ticketing.server.movie.service.dto.TicketsReservationDTO; import com.ticketing.server.movie.service.dto.TicketsSoldDTO; @@ -33,13 +36,13 @@ import org.springframework.validation.annotation.Validated; public class TicketServiceImpl implements TicketService { private final TicketRepository ticketRepository; - private final MovieTimeRepository movieTimeRepository; + private final TicketLockService ticketLockService; @Override public List getTickets(@NotNull Long movieTimeId) { MovieTime movieTime = movieTimeRepository.findById(movieTimeId) - .orElseThrow(ErrorCode::throwMovieTimeNotFound); + .orElseThrow(() -> new TicketingException(MOVIE_TIME_NOT_FOUND)); return ticketRepository.findValidTickets(movieTime) .stream() @@ -55,7 +58,7 @@ public class TicketServiceImpl implements TicketService { .collect(Collectors.toList()); if (ticketDetails.isEmpty()) { - throw ErrorCode.throwPaymentIdNotFound(); + throw new TicketingException(PAYMENT_ID_NOT_FOUND); } return ticketDetails; @@ -64,33 +67,13 @@ public class TicketServiceImpl implements TicketService { @Override @Transactional public TicketsReservationDTO ticketReservation(@NotEmptyCollection List ticketIds) { - List tickets = getTicketsByInTicketIds(ticketIds); - - Long firstMovieTimeId = firstMovieTimeId(tickets); - List reservationDtoList = tickets.stream() - .map(Ticket::makeReservation) - .filter(ticket -> firstMovieTimeId.equals(ticket.getMovieTimeId())) - .map(TicketReservationDTO::new) - .collect(Collectors.toList()); - - if (ticketIds.size() != reservationDtoList.size()) { - throw ErrorCode.throwBadRequestMovieTime(); - } - - return new TicketsReservationDTO(firstMovieTitle(tickets), reservationDtoList); + return ticketLockService.ticketReservation(new TicketIdsDTO(ticketIds)); } @Override @Transactional public TicketsSoldDTO ticketSold(@NotNull Long paymentId, @NotEmptyCollection List ticketIds) { - List tickets = getTicketsByInTicketIds(ticketIds); - - List soldDtoList = tickets.stream() - .map(ticket -> ticket.makeSold(paymentId)) - .map(TicketSoldDTO::new) - .collect(Collectors.toList()); - - return new TicketsSoldDTO(paymentId, soldDtoList); + return ticketLockService.ticketSold(paymentId, new TicketIdsDTO(ticketIds)); } @Override @@ -117,20 +100,10 @@ public class TicketServiceImpl implements TicketService { List tickets = ticketRepository.findTicketFetchJoinByTicketIds(ticketIds); if (tickets.size() != ticketIds.size()) { - throw ErrorCode.throwInvalidTicketId(); + throw new TicketingException(INVALID_TICKET_ID); } return tickets; } - private Long firstMovieTimeId(List tickets) { - Ticket ticket = tickets.get(0); - return ticket.getMovieTimeId(); - } - - private String firstMovieTitle(List tickets) { - Ticket ticket = tickets.get(0); - return ticket.getMovieTitle(); - } - } diff --git a/server/src/main/java/com/ticketing/server/movie/service/dto/TicketIdsDTO.java b/server/src/main/java/com/ticketing/server/movie/service/dto/TicketIdsDTO.java new file mode 100644 index 0000000..6f98bc4 --- /dev/null +++ b/server/src/main/java/com/ticketing/server/movie/service/dto/TicketIdsDTO.java @@ -0,0 +1,24 @@ +package com.ticketing.server.movie.service.dto; + +import static com.ticketing.server.movie.domain.TicketLock.LOCK_KEY; + +import com.ticketing.server.global.validator.constraints.NotEmptyCollection; +import java.util.List; +import java.util.stream.Collectors; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class TicketIdsDTO { + + @NotEmptyCollection + private List ticketIds; + + public List makeTicketLockIds() { + return ticketIds.stream() + .map(id -> LOCK_KEY.getValue() + ":" + id) + .collect(Collectors.toList()); + } + +} diff --git a/server/src/main/java/com/ticketing/server/movie/service/interfaces/TicketService.java b/server/src/main/java/com/ticketing/server/movie/service/interfaces/TicketService.java index b3dbb5c..2d8d8e7 100644 --- a/server/src/main/java/com/ticketing/server/movie/service/interfaces/TicketService.java +++ b/server/src/main/java/com/ticketing/server/movie/service/interfaces/TicketService.java @@ -3,6 +3,7 @@ package com.ticketing.server.movie.service.interfaces; import com.ticketing.server.global.validator.constraints.NotEmptyCollection; import com.ticketing.server.movie.domain.Ticket; import com.ticketing.server.movie.service.dto.TicketDTO; +import com.ticketing.server.movie.service.dto.TicketIdsDTO; import com.ticketing.server.movie.service.dto.TicketRefundDTO; import com.ticketing.server.movie.service.dto.TicketsCancelDTO; import com.ticketing.server.movie.service.dto.TicketsReservationDTO; @@ -10,6 +11,7 @@ import com.ticketing.server.movie.service.dto.TicketsSoldDTO; import com.ticketing.server.payment.service.dto.TicketDetailDTO; import java.util.List; import java.util.function.UnaryOperator; +import javax.validation.Valid; import javax.validation.constraints.NotNull; public interface TicketService { diff --git a/server/src/test/java/com/ticketing/server/movie/aop/TicketLockAspectTest.java b/server/src/test/java/com/ticketing/server/movie/aop/TicketLockAspectTest.java new file mode 100644 index 0000000..0c4d92d --- /dev/null +++ b/server/src/test/java/com/ticketing/server/movie/aop/TicketLockAspectTest.java @@ -0,0 +1,7 @@ +package com.ticketing.server.movie.aop; + +import static org.junit.jupiter.api.Assertions.*; + +class TicketLockAspectTest { + +} diff --git a/server/src/test/java/com/ticketing/server/movie/service/TicketLockServiceTest.java b/server/src/test/java/com/ticketing/server/movie/service/TicketLockServiceTest.java new file mode 100644 index 0000000..3789de8 --- /dev/null +++ b/server/src/test/java/com/ticketing/server/movie/service/TicketLockServiceTest.java @@ -0,0 +1,68 @@ +package com.ticketing.server.movie.service; + +import static com.ticketing.server.movie.domain.TicketTest.setupTickets; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.when; + +import com.ticketing.server.global.exception.ErrorCode; +import com.ticketing.server.global.exception.TicketingException; +import com.ticketing.server.movie.domain.Ticket; +import com.ticketing.server.movie.domain.repository.TicketRepository; +import com.ticketing.server.movie.service.dto.TicketIdsDTO; +import com.ticketing.server.movie.service.dto.TicketsReservationDTO; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class TicketLockServiceTest { + + @Mock + TicketRepository ticketRepository; + + @InjectMocks + TicketLockService ticketLockService; + + @Test + @DisplayName("티켓목록 예약으로 변경 시 조회된 갯수랑 다른 경우") + void ticketReservationFail() { + // given + List tickets = setupTickets(); + List list = List.of(tickets.get(0), tickets.get(1), tickets.get(2)); + List ids = List.of(0L, 1L, 2L, 10000L); + TicketIdsDTO ticketIdsDto = new TicketIdsDTO(ids); + + when(ticketRepository.findTicketFetchJoinByTicketIds(ids)).thenReturn(list); + + // when + // then + assertThatThrownBy(() -> ticketLockService.ticketReservation(ticketIdsDto)) + .isInstanceOf(TicketingException.class) + .extracting("errorCode") + .isEqualTo(ErrorCode.INVALID_TICKET_ID); + } + + @Test + @DisplayName("티켓목록 예약으로 변경 완료") + void ticketReservationSuccess() { + // given + List tickets = setupTickets(); + List list = List.of(tickets.get(0), tickets.get(1), tickets.get(2)); + List ids = List.of(0L, 1L, 2L); + TicketIdsDTO ticketIdsDto = new TicketIdsDTO(ids); + + when(ticketRepository.findTicketFetchJoinByTicketIds(ids)).thenReturn(list); + + // when + TicketsReservationDTO ticketReservationsDto = ticketLockService.ticketReservation(ticketIdsDto); + + // then + assertThat(ticketReservationsDto.getTicketReservationDtoList()).hasSize(3); + } + +} diff --git a/server/src/test/java/com/ticketing/server/movie/service/TicketServiceImplTest.java b/server/src/test/java/com/ticketing/server/movie/service/TicketServiceImplTest.java index 4c2bb86..dafdd6b 100644 --- a/server/src/test/java/com/ticketing/server/movie/service/TicketServiceImplTest.java +++ b/server/src/test/java/com/ticketing/server/movie/service/TicketServiceImplTest.java @@ -10,7 +10,6 @@ import com.ticketing.server.global.exception.ErrorCode; import com.ticketing.server.global.exception.TicketingException; import com.ticketing.server.movie.domain.Ticket; import com.ticketing.server.movie.domain.repository.TicketRepository; -import com.ticketing.server.movie.service.dto.TicketsReservationDTO; import com.ticketing.server.payment.service.dto.TicketDetailDTO; import java.util.Collections; import java.util.List; @@ -61,39 +60,4 @@ class TicketServiceImplTest { ); } - @Test - @DisplayName("티켓목록 예약으로 변경 시 조회된 갯수랑 다른 경우") - void ticketReservationFail() { - // given - List tickets = setupTickets(); - List list = List.of(tickets.get(0), tickets.get(1), tickets.get(2)); - List ids = List.of(0L, 1L, 2L, 10000L); - - when(ticketRepository.findTicketFetchJoinByTicketIds(ids)).thenReturn(list); - - // when - // then - assertThatThrownBy(() -> ticketService.ticketReservation(ids)) - .isInstanceOf(TicketingException.class) - .extracting("errorCode") - .isEqualTo(ErrorCode.INVALID_TICKET_ID); - } - - @Test - @DisplayName("티켓목록 예약으로 변경 완료") - void ticketReservationSuccess() { - // given - List tickets = setupTickets(); - List list = List.of(tickets.get(0), tickets.get(1), tickets.get(2)); - List ids = List.of(0L, 1L, 2L); - - when(ticketRepository.findTicketFetchJoinByTicketIds(ids)).thenReturn(list); - - // when - TicketsReservationDTO ticketReservationsDto = ticketService.ticketReservation(ids); - - // then - assertThat(ticketReservationsDto.getTicketReservationDtoList()).hasSize(3); - } - }