diff --git a/src/main/java/com/tiketeer/Tiketeer/auth/RequestMatcherHolder.java b/src/main/java/com/tiketeer/Tiketeer/auth/RequestMatcherHolder.java index ca071aa0..a92559a2 100644 --- a/src/main/java/com/tiketeer/Tiketeer/auth/RequestMatcherHolder.java +++ b/src/main/java/com/tiketeer/Tiketeer/auth/RequestMatcherHolder.java @@ -27,6 +27,7 @@ public class RequestMatcherHolder { new RequestInfo(GET, "/members/*", RoleEnum.BUYER), new RequestInfo(GET, "/members", RoleEnum.BUYER), new RequestInfo(GET, "/members/*/purchases", RoleEnum.BUYER), + new RequestInfo(GET, "/members/*/purchases/*", RoleEnum.BUYER), new RequestInfo(GET, "/members/*/sale", RoleEnum.SELLER), new RequestInfo(POST, "/members/*/points", RoleEnum.BUYER), @@ -42,9 +43,13 @@ public class RequestMatcherHolder { new RequestInfo(PATCH, "/ticketings/*", RoleEnum.SELLER), new RequestInfo(DELETE, "/ticketings/*", RoleEnum.SELLER), + //ticket + new RequestInfo(POST, "/ticketings/*/tickets", RoleEnum.SELLER), + new RequestInfo(PUT, "/ticketings/*/tickets", RoleEnum.SELLER), + // purchase new RequestInfo(POST, "/purchases", RoleEnum.BUYER), - new RequestInfo(DELETE, "/purchases/*/tickets", RoleEnum.BUYER), + new RequestInfo(DELETE, "/purchases/*/items", RoleEnum.BUYER), // swagger new RequestInfo(GET, "/v3/api-docs/**", null), diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/member/controller/MemberController.java b/src/main/java/com/tiketeer/Tiketeer/domain/member/controller/MemberController.java index 0aa273c7..e3eedeed 100644 --- a/src/main/java/com/tiketeer/Tiketeer/domain/member/controller/MemberController.java +++ b/src/main/java/com/tiketeer/Tiketeer/domain/member/controller/MemberController.java @@ -18,7 +18,8 @@ import com.tiketeer.Tiketeer.auth.SecurityContextHelper; import com.tiketeer.Tiketeer.domain.member.controller.dto.ChargePointRequestDto; import com.tiketeer.Tiketeer.domain.member.controller.dto.ChargePointResponseDto; -import com.tiketeer.Tiketeer.domain.member.controller.dto.GetMemberPurchasesResponseDto; +import com.tiketeer.Tiketeer.domain.member.controller.dto.GetMemberAllPurchasesResponseDto; +import com.tiketeer.Tiketeer.domain.member.controller.dto.GetMemberPurchaseResponseDto; import com.tiketeer.Tiketeer.domain.member.controller.dto.GetMemberResponseDto; import com.tiketeer.Tiketeer.domain.member.controller.dto.GetMemberTicketingSalesResponseDto; import com.tiketeer.Tiketeer.domain.member.controller.dto.MemberRegisterRequestDto; @@ -26,15 +27,17 @@ import com.tiketeer.Tiketeer.domain.member.controller.dto.ResetPasswordRequestDto; import com.tiketeer.Tiketeer.domain.member.usecase.ChargeMemberPointUseCase; import com.tiketeer.Tiketeer.domain.member.usecase.DeleteMemberUseCase; -import com.tiketeer.Tiketeer.domain.member.usecase.GetMemberPurchasesUseCase; +import com.tiketeer.Tiketeer.domain.member.usecase.GetMemberAllPurchasesUseCase; +import com.tiketeer.Tiketeer.domain.member.usecase.GetMemberPurchaseUseCase; import com.tiketeer.Tiketeer.domain.member.usecase.GetMemberTicketingSalesUseCase; import com.tiketeer.Tiketeer.domain.member.usecase.GetMemberUseCase; import com.tiketeer.Tiketeer.domain.member.usecase.MemberRegisterUseCase; import com.tiketeer.Tiketeer.domain.member.usecase.ResetPasswordUseCase; import com.tiketeer.Tiketeer.domain.member.usecase.SendPasswordChangeEmailUseCase; import com.tiketeer.Tiketeer.domain.member.usecase.dto.DeleteMemberCommandDto; +import com.tiketeer.Tiketeer.domain.member.usecase.dto.GetMemberAllPurchasesCommandDto; import com.tiketeer.Tiketeer.domain.member.usecase.dto.GetMemberCommandDto; -import com.tiketeer.Tiketeer.domain.member.usecase.dto.GetMemberPurchasesCommandDto; +import com.tiketeer.Tiketeer.domain.member.usecase.dto.GetMemberPurchaseCommandDto; import com.tiketeer.Tiketeer.domain.member.usecase.dto.GetMemberTicketingSalesCommandDto; import com.tiketeer.Tiketeer.domain.member.usecase.dto.MemberRegisterCommandDto; import com.tiketeer.Tiketeer.domain.member.usecase.dto.SendPwdChangeEmailCommandDto; @@ -49,7 +52,8 @@ public class MemberController { private final ChargeMemberPointUseCase chargeMemberPointUseCase; private final GetMemberTicketingSalesUseCase getMemberTicketingSalesUseCase; private final GetMemberUseCase getMemberUseCase; - private final GetMemberPurchasesUseCase getMemberPurchasesUseCase; + private final GetMemberAllPurchasesUseCase getMemberAllPurchasesUseCase; + private final GetMemberPurchaseUseCase getMemberPurchasesUseCase; private final ResetPasswordUseCase resetPasswordUseCase; private final SendPasswordChangeEmailUseCase sendPasswordChangeEmailUseCase; private final DeleteMemberUseCase deleteMemberUseCase; @@ -59,7 +63,8 @@ public class MemberController { public MemberController(MemberRegisterUseCase memberRegisterUseCase, ChargeMemberPointUseCase chargeMemberPointUseCase, ResetPasswordUseCase resetPasswordUseCase, GetMemberTicketingSalesUseCase getMemberTicketingSalesUseCase, - GetMemberUseCase getMemberUseCase, GetMemberPurchasesUseCase getMemberPurchasesUseCase, + GetMemberUseCase getMemberUseCase, GetMemberAllPurchasesUseCase getMemberAllPurchasesUseCase, + GetMemberPurchaseUseCase getMemberPurchasesUseCase, SendPasswordChangeEmailUseCase sendPasswordChangeEmailUseCase, DeleteMemberUseCase deleteMemberUseCase, SecurityContextHelper securityContextHelper) { @@ -68,6 +73,7 @@ public MemberController(MemberRegisterUseCase memberRegisterUseCase, this.getMemberTicketingSalesUseCase = getMemberTicketingSalesUseCase; this.getMemberUseCase = getMemberUseCase; this.resetPasswordUseCase = resetPasswordUseCase; + this.getMemberAllPurchasesUseCase = getMemberAllPurchasesUseCase; this.getMemberPurchasesUseCase = getMemberPurchasesUseCase; this.sendPasswordChangeEmailUseCase = sendPasswordChangeEmailUseCase; this.deleteMemberUseCase = deleteMemberUseCase; @@ -105,13 +111,23 @@ public ResponseEntity> chargePoint(@PathVari } @GetMapping("/{memberId}/purchases") - public ResponseEntity>> getMemberPurchases( + public ResponseEntity>> getMemberAllPurchases( @PathVariable UUID memberId) { var email = securityContextHelper.getEmailInToken(); - var results = getMemberPurchasesUseCase.getMemberPurchases( - GetMemberPurchasesCommandDto.builder().memberEmail(email).build()); + var results = getMemberAllPurchasesUseCase.getMemberPurchases( + GetMemberAllPurchasesCommandDto.builder().memberEmail(email).build()); var responseBody = ApiResponse.wrap( - results.stream().map(GetMemberPurchasesResponseDto::convertFromDto).toList()); + results.stream().map(GetMemberAllPurchasesResponseDto::convertFromDto).toList()); + return ResponseEntity.status(HttpStatus.OK).body(responseBody); + } + + @GetMapping("/{memberId}/purchases/{purchaseId}") + public ResponseEntity> getMemberPurchase( + @PathVariable UUID memberId, @PathVariable UUID purchaseId) { + var email = securityContextHelper.getEmailInToken(); + var result = getMemberPurchasesUseCase.getMemberPurchases( + GetMemberPurchaseCommandDto.builder().memberEmail(email).purchaseId(purchaseId).build()); + var responseBody = ApiResponse.wrap(GetMemberPurchaseResponseDto.convertFromDto(result)); return ResponseEntity.status(HttpStatus.OK).body(responseBody); } diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/member/controller/dto/GetMemberAllPurchasesResponseDto.java b/src/main/java/com/tiketeer/Tiketeer/domain/member/controller/dto/GetMemberAllPurchasesResponseDto.java new file mode 100644 index 00000000..c9a9628d --- /dev/null +++ b/src/main/java/com/tiketeer/Tiketeer/domain/member/controller/dto/GetMemberAllPurchasesResponseDto.java @@ -0,0 +1,51 @@ +package com.tiketeer.Tiketeer.domain.member.controller.dto; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +import com.tiketeer.Tiketeer.domain.member.usecase.dto.GetMemberAllPurchasesResultDto; + +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString +public final class GetMemberAllPurchasesResponseDto { + private final UUID purchaseId; + private final UUID ticketingId; + private final String title; + private final LocalDateTime eventTime; + private final LocalDateTime createdAt; + private final String category; + private final String thumbnailPath; + private final List purchaseItems; + + @Builder + public GetMemberAllPurchasesResponseDto(UUID purchaseId, UUID ticketingId, String title, + LocalDateTime eventTime, LocalDateTime createdAt, String category, String thumbnailPath, + List purchaseItems) { + this.purchaseId = purchaseId; + this.ticketingId = ticketingId; + this.title = title; + this.eventTime = eventTime; + this.createdAt = createdAt; + this.category = category; + this.thumbnailPath = thumbnailPath; + this.purchaseItems = purchaseItems; + } + + public static GetMemberAllPurchasesResponseDto convertFromDto(GetMemberAllPurchasesResultDto dto) { + return GetMemberAllPurchasesResponseDto.builder() + .purchaseId(dto.getPurchaseId()) + .ticketingId(dto.getTicketingId()) + .title(dto.getTitle()) + .eventTime(dto.getEventTime()) + .createdAt(dto.getCreatedAt()) + .category(dto.getCategory()) + .thumbnailPath(dto.getThumbnailPath()) + .purchaseItems(dto.getPurchaseItems()) + .build(); + } +} diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/member/controller/dto/GetMemberPurchaseResponseDto.java b/src/main/java/com/tiketeer/Tiketeer/domain/member/controller/dto/GetMemberPurchaseResponseDto.java new file mode 100644 index 00000000..12681708 --- /dev/null +++ b/src/main/java/com/tiketeer/Tiketeer/domain/member/controller/dto/GetMemberPurchaseResponseDto.java @@ -0,0 +1,58 @@ +package com.tiketeer.Tiketeer.domain.member.controller.dto; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +import com.tiketeer.Tiketeer.domain.member.usecase.dto.GetMemberPurchaseResultDto; + +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString +public final class GetMemberPurchaseResponseDto { + private final UUID purchaseId; + private final UUID ticketingId; + private final String title; + private final String location; + private final LocalDateTime eventTime; + private final LocalDateTime createdAt; + private final String category; + private final int runningMinute; + private final String thumbnailPath; + private final List purchaseItems; + + @Builder + public GetMemberPurchaseResponseDto(UUID purchaseId, UUID ticketingId, String title, String location, + LocalDateTime eventTime, LocalDateTime createdAt, String category, + int runningMinute, String thumbnailPath, + List purchaseItems) { + this.purchaseId = purchaseId; + this.ticketingId = ticketingId; + this.title = title; + this.location = location; + this.eventTime = eventTime; + this.createdAt = createdAt; + this.category = category; + this.runningMinute = runningMinute; + this.thumbnailPath = thumbnailPath; + this.purchaseItems = purchaseItems; + } + + public static GetMemberPurchaseResponseDto convertFromDto(GetMemberPurchaseResultDto dto) { + return GetMemberPurchaseResponseDto.builder() + .purchaseId(dto.getPurchaseId()) + .ticketingId(dto.getTicketingId()) + .title(dto.getTitle()) + .location(dto.getLocation()) + .eventTime(dto.getEventTime()) + .createdAt(dto.getCreatedAt()) + .category(dto.getCategory()) + .runningMinute(dto.getRunningMinutes()) + .thumbnailPath(dto.getThumbnailPath()) + .purchaseItems(dto.getPurchaseItems()) + .build(); + } +} diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/member/controller/dto/GetMemberPurchasesResponseDto.java b/src/main/java/com/tiketeer/Tiketeer/domain/member/controller/dto/GetMemberPurchasesResponseDto.java deleted file mode 100644 index 50a6d216..00000000 --- a/src/main/java/com/tiketeer/Tiketeer/domain/member/controller/dto/GetMemberPurchasesResponseDto.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.tiketeer.Tiketeer.domain.member.controller.dto; - -import java.time.LocalDateTime; -import java.util.UUID; - -import com.tiketeer.Tiketeer.domain.member.usecase.dto.GetMemberPurchasesResultDto; - -import lombok.Builder; -import lombok.Getter; -import lombok.ToString; - -@Getter -@ToString -public class GetMemberPurchasesResponseDto { - private UUID purchaseId; - private UUID ticketingId; - private String title; - private String location; - private LocalDateTime eventTime; - private LocalDateTime saleStart; - private LocalDateTime saleEnd; - private LocalDateTime createdAt; - private String category; - private Long price; - private Long count; - - @Builder - public GetMemberPurchasesResponseDto(UUID purchaseId, UUID ticketingId, String title, String location, - LocalDateTime eventTime, LocalDateTime saleStart, LocalDateTime saleEnd, LocalDateTime createdAt, - String category, Long price, Long count) { - this.purchaseId = purchaseId; - this.ticketingId = ticketingId; - this.title = title; - this.location = location; - this.eventTime = eventTime; - this.saleStart = saleStart; - this.saleEnd = saleEnd; - this.createdAt = createdAt; - this.category = category; - this.price = price; - this.count = count; - } - - public static GetMemberPurchasesResponseDto convertFromDto(GetMemberPurchasesResultDto dto) { - return GetMemberPurchasesResponseDto.builder() - .purchaseId(dto.getPurchaseId()) - .ticketingId(dto.getTicketingId()) - .title(dto.getTitle()) - .location(dto.getLocation()) - .eventTime(dto.getEventTime()) - .saleStart(dto.getSaleStart()) - .saleEnd(dto.getSaleEnd()) - .createdAt(dto.getCreatedAt()) - .category(dto.getCategory()) - .price(dto.getPrice()) - .count(dto.getCount()) - .build(); - } -} diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/member/usecase/GetMemberPurchasesUseCase.java b/src/main/java/com/tiketeer/Tiketeer/domain/member/usecase/GetMemberAllPurchasesUseCase.java similarity index 68% rename from src/main/java/com/tiketeer/Tiketeer/domain/member/usecase/GetMemberPurchasesUseCase.java rename to src/main/java/com/tiketeer/Tiketeer/domain/member/usecase/GetMemberAllPurchasesUseCase.java index b8b95eed..6bf33c4a 100644 --- a/src/main/java/com/tiketeer/Tiketeer/domain/member/usecase/GetMemberPurchasesUseCase.java +++ b/src/main/java/com/tiketeer/Tiketeer/domain/member/usecase/GetMemberAllPurchasesUseCase.java @@ -8,24 +8,25 @@ import com.tiketeer.Tiketeer.domain.member.exception.MemberNotFoundException; import com.tiketeer.Tiketeer.domain.member.repository.MemberRepository; -import com.tiketeer.Tiketeer.domain.member.usecase.dto.GetMemberPurchasesCommandDto; -import com.tiketeer.Tiketeer.domain.member.usecase.dto.GetMemberPurchasesResultDto; +import com.tiketeer.Tiketeer.domain.member.usecase.dto.GetMemberAllPurchasesCommandDto; +import com.tiketeer.Tiketeer.domain.member.usecase.dto.GetMemberAllPurchasesResultDto; import com.tiketeer.Tiketeer.domain.purchase.repository.PurchaseRepository; @Service @Transactional(readOnly = true) -public class GetMemberPurchasesUseCase { +public class GetMemberAllPurchasesUseCase { private final MemberRepository memberRepository; private final PurchaseRepository purchaseRepository; @Autowired - public GetMemberPurchasesUseCase(MemberRepository memberRepository, PurchaseRepository purchaseRepository) { + public GetMemberAllPurchasesUseCase(MemberRepository memberRepository, PurchaseRepository purchaseRepository) { this.memberRepository = memberRepository; this.purchaseRepository = purchaseRepository; } - public List getMemberPurchases(GetMemberPurchasesCommandDto command) { + public List getMemberPurchases(GetMemberAllPurchasesCommandDto command) { var member = memberRepository.findByEmail(command.getMemberEmail()).orElseThrow(MemberNotFoundException::new); - return purchaseRepository.findWithTicketingByMember(member); + var results = purchaseRepository.findWithTicketingByMember(member); + return results.stream().map(GetMemberAllPurchasesResultDto::new).toList(); } } diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/member/usecase/GetMemberPurchaseUseCase.java b/src/main/java/com/tiketeer/Tiketeer/domain/member/usecase/GetMemberPurchaseUseCase.java new file mode 100644 index 00000000..26585f48 --- /dev/null +++ b/src/main/java/com/tiketeer/Tiketeer/domain/member/usecase/GetMemberPurchaseUseCase.java @@ -0,0 +1,34 @@ +package com.tiketeer.Tiketeer.domain.member.usecase; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.tiketeer.Tiketeer.domain.member.exception.MemberNotFoundException; +import com.tiketeer.Tiketeer.domain.member.repository.MemberRepository; +import com.tiketeer.Tiketeer.domain.member.usecase.dto.GetMemberPurchaseCommandDto; +import com.tiketeer.Tiketeer.domain.member.usecase.dto.GetMemberPurchaseResultDto; +import com.tiketeer.Tiketeer.domain.purchase.exception.PurchaseNotFoundException; +import com.tiketeer.Tiketeer.domain.purchase.repository.PurchaseRepository; + +@Service +@Transactional(readOnly = true) +public class GetMemberPurchaseUseCase { + private final MemberRepository memberRepository; + private final PurchaseRepository purchaseRepository; + + @Autowired + public GetMemberPurchaseUseCase(MemberRepository memberRepository, PurchaseRepository purchaseRepository) { + this.memberRepository = memberRepository; + this.purchaseRepository = purchaseRepository; + } + + public GetMemberPurchaseResultDto getMemberPurchases(GetMemberPurchaseCommandDto command) { + var member = memberRepository.findByEmail(command.getMemberEmail()).orElseThrow(MemberNotFoundException::new); + var results = purchaseRepository.findWithTicketingByMember(member); + if (results.isEmpty()) { + throw new PurchaseNotFoundException(); + } + return new GetMemberPurchaseResultDto(results.getFirst()); + } +} diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/member/usecase/dto/GetMemberPurchasesCommandDto.java b/src/main/java/com/tiketeer/Tiketeer/domain/member/usecase/dto/GetMemberAllPurchasesCommandDto.java similarity index 67% rename from src/main/java/com/tiketeer/Tiketeer/domain/member/usecase/dto/GetMemberPurchasesCommandDto.java rename to src/main/java/com/tiketeer/Tiketeer/domain/member/usecase/dto/GetMemberAllPurchasesCommandDto.java index 6fd2d0d2..649822f8 100644 --- a/src/main/java/com/tiketeer/Tiketeer/domain/member/usecase/dto/GetMemberPurchasesCommandDto.java +++ b/src/main/java/com/tiketeer/Tiketeer/domain/member/usecase/dto/GetMemberAllPurchasesCommandDto.java @@ -6,11 +6,11 @@ @Getter @ToString -public class GetMemberPurchasesCommandDto { +public class GetMemberAllPurchasesCommandDto { private final String memberEmail; @Builder - public GetMemberPurchasesCommandDto(String memberEmail) { + public GetMemberAllPurchasesCommandDto(String memberEmail) { this.memberEmail = memberEmail; } } diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/member/usecase/dto/GetMemberAllPurchasesResultDto.java b/src/main/java/com/tiketeer/Tiketeer/domain/member/usecase/dto/GetMemberAllPurchasesResultDto.java new file mode 100644 index 00000000..ab4bb10b --- /dev/null +++ b/src/main/java/com/tiketeer/Tiketeer/domain/member/usecase/dto/GetMemberAllPurchasesResultDto.java @@ -0,0 +1,78 @@ +package com.tiketeer.Tiketeer.domain.member.usecase.dto; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@Getter +@ToString +public final class GetMemberAllPurchasesResultDto { + private final UUID purchaseId; + private final UUID ticketingId; + private final String title; + private final LocalDateTime eventTime; + private final LocalDateTime createdAt; + private final String category; + private final String thumbnailPath; + private final List purchaseItems; + + @Builder + public GetMemberAllPurchasesResultDto(UUID purchaseId, UUID ticketingId, String title, String location, + LocalDateTime eventTime, LocalDateTime createdAt, String category, + String thumbnailPath, List purchaseItems) { + this.purchaseId = purchaseId; + this.ticketingId = ticketingId; + this.title = title; + this.eventTime = eventTime; + this.createdAt = createdAt; + this.category = category; + this.thumbnailPath = thumbnailPath; + this.purchaseItems = purchaseItems; + } + + @Builder + public GetMemberAllPurchasesResultDto(GetMemberPurchase purchase) { + this.purchaseId = purchase.getPurchaseId(); + this.ticketingId = purchase.getTicketingId(); + this.title = purchase.getTitle(); + this.eventTime = purchase.getEventTime(); + this.category = purchase.getCategory(); + this.createdAt = purchase.getCreatedAt(); + this.thumbnailPath = purchase.getThumbnailPath(); + this.purchaseItems = purchase.getPurchaseItems().stream().map(PurchaseItem::new).toList(); + + } + + @Getter + @ToString + @NoArgsConstructor(force = true) + public static class PurchaseItem { + private final UUID purchaseItemId; + private final UUID ticketId; + private final String title; + private final Long price; + + @Builder + public PurchaseItem(UUID purchaseItemId, UUID ticketId, String title, Long price) { + this.purchaseItemId = purchaseItemId; + this.ticketId = ticketId; + this.title = title; + this.price = price; + } + + @Builder + public PurchaseItem(GetMemberPurchase.GetMemberPurchaseItems pi) { + this.purchaseItemId = pi.getPurchaseItemId(); + this.ticketId = pi.getTicketId(); + this.title = pi.getTitle(); + this.price = pi.getPrice(); + } + } + +} + diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/member/usecase/dto/GetMemberPurchase.java b/src/main/java/com/tiketeer/Tiketeer/domain/member/usecase/dto/GetMemberPurchase.java new file mode 100644 index 00000000..dc528632 --- /dev/null +++ b/src/main/java/com/tiketeer/Tiketeer/domain/member/usecase/dto/GetMemberPurchase.java @@ -0,0 +1,57 @@ +package com.tiketeer.Tiketeer.domain.member.usecase.dto; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +import org.springframework.beans.factory.annotation.Value; + +public interface GetMemberPurchase { + @Value("#{target.id}") + UUID getPurchaseId(); + + @Value("#{target.purchaseItems[0].ticket.ticketing.id}") + UUID getTicketingId(); + + @Value("#{target.purchaseItems[0].ticket.ticketing.title}") + String getTitle(); + + @Value("#{target.purchaseItems[0].ticket.ticketing.location}") + String getLocation(); + + @Value("#{target.purchaseItems[0].ticket.ticketing.eventTime}") + LocalDateTime getEventTime(); + + @Value("#{target.purchaseItems[0].ticket.ticketing.createdAt}") + LocalDateTime getCreatedAt(); + + @Value("#{target.purchaseItems[0].ticket.ticketing.category}") + String getCategory(); + + @Value("#{target.purchaseItems[0].ticket.ticketing.runningMinutes}") + int getRunningMinutes(); + + @Value("#{target.purchaseItems[0].ticket.ticketing.thumbnailPath}") + String getThumbnailPath(); + + List getPurchaseItems(); + + interface GetMemberPurchaseItems { + + @Value("#{target.id}") + UUID getPurchaseItemId(); + + @Value("#{target.ticket.id}") + UUID getTicketId(); + + @Value("#{target.ticket.title}") + String getTitle(); + + @Value("#{target.ticket.description}") + String getDescription(); + + @Value("#{target.ticket.price}") + Long getPrice(); + } + +} diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/dto/GetPurchaseTicketsCommandDto.java b/src/main/java/com/tiketeer/Tiketeer/domain/member/usecase/dto/GetMemberPurchaseCommandDto.java similarity index 59% rename from src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/dto/GetPurchaseTicketsCommandDto.java rename to src/main/java/com/tiketeer/Tiketeer/domain/member/usecase/dto/GetMemberPurchaseCommandDto.java index 6dfac4e6..2fa65ad9 100644 --- a/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/dto/GetPurchaseTicketsCommandDto.java +++ b/src/main/java/com/tiketeer/Tiketeer/domain/member/usecase/dto/GetMemberPurchaseCommandDto.java @@ -1,4 +1,4 @@ -package com.tiketeer.Tiketeer.domain.purchase.usecase.dto; +package com.tiketeer.Tiketeer.domain.member.usecase.dto; import java.util.UUID; @@ -8,13 +8,13 @@ @Getter @ToString -public class GetPurchaseTicketsCommandDto { - private final UUID purchaseId; +public class GetMemberPurchaseCommandDto { private final String memberEmail; + private final UUID purchaseId; @Builder - public GetPurchaseTicketsCommandDto(UUID purchaseId, String memberEmail) { - this.purchaseId = purchaseId; + public GetMemberPurchaseCommandDto(String memberEmail, UUID purchaseId) { this.memberEmail = memberEmail; + this.purchaseId = purchaseId; } } diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/member/usecase/dto/GetMemberPurchaseResultDto.java b/src/main/java/com/tiketeer/Tiketeer/domain/member/usecase/dto/GetMemberPurchaseResultDto.java new file mode 100644 index 00000000..5fbad686 --- /dev/null +++ b/src/main/java/com/tiketeer/Tiketeer/domain/member/usecase/dto/GetMemberPurchaseResultDto.java @@ -0,0 +1,87 @@ +package com.tiketeer.Tiketeer.domain.member.usecase.dto; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@Getter +@ToString +public final class GetMemberPurchaseResultDto { + private final UUID purchaseId; + private final UUID ticketingId; + private final String title; + private final String location; + private final LocalDateTime eventTime; + private final LocalDateTime createdAt; + private final String category; + private final int runningMinutes; + private final String thumbnailPath; + private final List purchaseItems; + + @Builder + public GetMemberPurchaseResultDto(UUID purchaseId, UUID ticketingId, String title, String location, + LocalDateTime eventTime, LocalDateTime createdAt, String category, + int runningMinutes, String thumbnailPath, List purchaseItems) { + this.purchaseId = purchaseId; + this.ticketingId = ticketingId; + this.title = title; + this.location = location; + this.eventTime = eventTime; + this.createdAt = createdAt; + this.category = category; + this.runningMinutes = runningMinutes; + this.thumbnailPath = thumbnailPath; + this.purchaseItems = purchaseItems; + } + + @Builder + public GetMemberPurchaseResultDto(GetMemberPurchase purchase) { + this.purchaseId = purchase.getPurchaseId(); + this.ticketingId = purchase.getTicketingId(); + this.title = purchase.getTitle(); + this.location = purchase.getLocation(); + this.eventTime = purchase.getEventTime(); + this.category = purchase.getCategory(); + this.createdAt = purchase.getCreatedAt(); + this.runningMinutes = purchase.getRunningMinutes(); + this.thumbnailPath = purchase.getThumbnailPath(); + this.purchaseItems = purchase.getPurchaseItems().stream().map(PurchaseItem::new).toList(); + + } + + @Getter + @ToString + @NoArgsConstructor(force = true) + public static class PurchaseItem { + private final UUID purchaseItemId; + private final UUID ticketId; + private final String title; + private final String description; + private final Long price; + + @Builder + public PurchaseItem(UUID purchaseItemId, UUID ticketId, String title, String description, Long price) { + this.purchaseItemId = purchaseItemId; + this.ticketId = ticketId; + this.title = title; + this.description = description; + this.price = price; + } + + @Builder + public PurchaseItem(GetMemberPurchase.GetMemberPurchaseItems pi) { + this.purchaseItemId = pi.getPurchaseItemId(); + this.ticketId = pi.getTicketId(); + this.title = pi.getTitle(); + this.description = pi.getDescription(); + this.price = pi.getPrice(); + } + } + +} + diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/member/usecase/dto/GetMemberPurchasesResultDto.java b/src/main/java/com/tiketeer/Tiketeer/domain/member/usecase/dto/GetMemberPurchasesResultDto.java deleted file mode 100644 index 2a06e401..00000000 --- a/src/main/java/com/tiketeer/Tiketeer/domain/member/usecase/dto/GetMemberPurchasesResultDto.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.tiketeer.Tiketeer.domain.member.usecase.dto; - -import java.time.LocalDateTime; -import java.util.UUID; - -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.ToString; - -@Getter -@ToString -@NoArgsConstructor(force = true) -public class GetMemberPurchasesResultDto { - private UUID purchaseId; - private UUID ticketingId; - private String title; - private String location; - private LocalDateTime eventTime; - private LocalDateTime saleStart; - private LocalDateTime saleEnd; - private LocalDateTime createdAt; - private String category; - private Long price; - private Long count; - - @Builder - public GetMemberPurchasesResultDto(UUID purchaseId, UUID ticketingId, String title, String location, - LocalDateTime eventTime, LocalDateTime saleStart, LocalDateTime saleEnd, LocalDateTime createdAt, - String category, Long price, Long count) { - this.purchaseId = purchaseId; - this.ticketingId = ticketingId; - this.title = title; - this.location = location; - this.eventTime = eventTime; - this.saleStart = saleStart; - this.saleEnd = saleEnd; - this.createdAt = createdAt; - this.category = category; - this.price = price; - this.count = count; - } -} diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/member/usecase/dto/GetMemberTicketingSalesResultDto.java b/src/main/java/com/tiketeer/Tiketeer/domain/member/usecase/dto/GetMemberTicketingSalesResultDto.java index 1a40ad3e..41ca10d5 100644 --- a/src/main/java/com/tiketeer/Tiketeer/domain/member/usecase/dto/GetMemberTicketingSalesResultDto.java +++ b/src/main/java/com/tiketeer/Tiketeer/domain/member/usecase/dto/GetMemberTicketingSalesResultDto.java @@ -12,6 +12,19 @@ @ToString @NoArgsConstructor(force = true) public class GetMemberTicketingSalesResultDto { + private final UUID ticketingId; + private final String title; + private final String description; + private final String location; + private final LocalDateTime eventTime; + private final LocalDateTime saleStart; + private final LocalDateTime saleEnd; + private final Long stock; + private final Long remainStock; + private final LocalDateTime createdAt; + private final String category; + private final int runningMinutes; + private final Long price; @Builder public GetMemberTicketingSalesResultDto( @@ -22,12 +35,12 @@ public GetMemberTicketingSalesResultDto( LocalDateTime eventTime, LocalDateTime saleStart, LocalDateTime saleEnd, - long stock, - long remainStock, + Long stock, + Long remainStock, LocalDateTime createdAt, String category, int runningMinutes, - long price + Long price ) { this.ticketingId = ticketingId; this.title = title; @@ -43,19 +56,4 @@ public GetMemberTicketingSalesResultDto( this.runningMinutes = runningMinutes; this.price = price; } - - private final UUID ticketingId; - private final String title; - private final String description; - private final String location; - private final LocalDateTime eventTime; - private final LocalDateTime saleStart; - private final LocalDateTime saleEnd; - private final long stock; - private final long remainStock; - private final LocalDateTime createdAt; - private final String category; - private final int runningMinutes; - private final long price; - } diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/purchase/Purchase.java b/src/main/java/com/tiketeer/Tiketeer/domain/purchase/Purchase.java index 0b6db6ef..7b672c02 100644 --- a/src/main/java/com/tiketeer/Tiketeer/domain/purchase/Purchase.java +++ b/src/main/java/com/tiketeer/Tiketeer/domain/purchase/Purchase.java @@ -1,6 +1,8 @@ package com.tiketeer.Tiketeer.domain.purchase; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; import org.hibernate.annotations.UuidGenerator; @@ -17,6 +19,7 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import jakarta.persistence.Temporal; import jakarta.persistence.TemporalType; @@ -25,7 +28,6 @@ import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.ToString; @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -33,7 +35,6 @@ @EntityListeners(AuditingEntityListener.class) @Getter @EqualsAndHashCode -@ToString public class Purchase { @Id @UuidGenerator @@ -44,6 +45,9 @@ public class Purchase { @JoinColumn(name = "member_id", foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT)) private Member member; + @OneToMany(mappedBy = "purchase") + private List purchaseItems = new ArrayList<>(); + @CreatedDate @Temporal(TemporalType.TIMESTAMP) @Column(name = "created_at", nullable = false) diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/purchase/PurchaseItem.java b/src/main/java/com/tiketeer/Tiketeer/domain/purchase/PurchaseItem.java new file mode 100644 index 00000000..e0592a22 --- /dev/null +++ b/src/main/java/com/tiketeer/Tiketeer/domain/purchase/PurchaseItem.java @@ -0,0 +1,73 @@ +package com.tiketeer.Tiketeer.domain.purchase; + +import java.time.LocalDateTime; +import java.util.UUID; + +import org.hibernate.annotations.UuidGenerator; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import com.tiketeer.Tiketeer.domain.ticket.Ticket; + +import jakarta.persistence.Column; +import jakarta.persistence.ConstraintMode; +import jakarta.persistence.Entity; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.ForeignKey; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "purchase_items") +@EntityListeners(AuditingEntityListener.class) +@Getter +@EqualsAndHashCode +@ToString +public class PurchaseItem { + @Id + @UuidGenerator + @Column(name = "purchase_item_id", nullable = false, updatable = false) + private UUID id; + + @ManyToOne + @JoinColumn(name = "purchase_id", foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT)) + private Purchase purchase; + + @ManyToOne + @JoinColumn(name = "ticket_id", foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT)) + private Ticket ticket; + + @CreatedDate + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "created_at", nullable = false) + private LocalDateTime createdAt; + + @Builder + public PurchaseItem(Purchase purchase, Ticket ticket) { + setPurchase(purchase); + this.ticket = ticket; + } + + public void setPurchase(Purchase purchase) { + if (this.purchase != null) { + this.purchase.getPurchaseItems().remove(this); + } + this.purchase = purchase; + + if (purchase == null) { + return; + } + this.purchase.getPurchaseItems().add(this); + } +} diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/purchase/controller/PurchaseController.java b/src/main/java/com/tiketeer/Tiketeer/domain/purchase/controller/PurchaseController.java index facfb7ef..12122579 100644 --- a/src/main/java/com/tiketeer/Tiketeer/domain/purchase/controller/PurchaseController.java +++ b/src/main/java/com/tiketeer/Tiketeer/domain/purchase/controller/PurchaseController.java @@ -6,7 +6,6 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -15,14 +14,11 @@ import com.tiketeer.Tiketeer.auth.SecurityContextHelper; import com.tiketeer.Tiketeer.domain.purchase.annotation.CheckWaitingQueueAndToken; -import com.tiketeer.Tiketeer.domain.purchase.controller.dto.DeletePurchaseTicketsRequestDto; +import com.tiketeer.Tiketeer.domain.purchase.controller.dto.DeletePurchaseItemsRequestDto; import com.tiketeer.Tiketeer.domain.purchase.controller.dto.PostPurchaseRequestDto; import com.tiketeer.Tiketeer.domain.purchase.controller.dto.PostPurchaseResponseDto; import com.tiketeer.Tiketeer.domain.purchase.usecase.CreatePurchaseUseCase; -import com.tiketeer.Tiketeer.domain.purchase.usecase.DeletePurchaseTicketsUseCase; -import com.tiketeer.Tiketeer.domain.purchase.usecase.GetPurchaseTicketsUseCase; -import com.tiketeer.Tiketeer.domain.purchase.usecase.dto.GetPurchaseTicketsCommandDto; -import com.tiketeer.Tiketeer.domain.purchase.usecase.dto.GetPurchaseTicketsResponseDto; +import com.tiketeer.Tiketeer.domain.purchase.usecase.DeletePurchaseItemsUseCase; import com.tiketeer.Tiketeer.response.ApiResponse; import jakarta.validation.Valid; @@ -31,17 +27,14 @@ @RequestMapping("/purchases") public class PurchaseController { private final CreatePurchaseUseCase createPurchaseUseCase; - private final DeletePurchaseTicketsUseCase deletePurchaseTicketsUseCase; - private final GetPurchaseTicketsUseCase getPurchaseTicketsUseCase; + private final DeletePurchaseItemsUseCase deletePurchaseItemsUseCase; private final SecurityContextHelper securityContextHelper; @Autowired PurchaseController(CreatePurchaseUseCase createPurchaseUseCase, - DeletePurchaseTicketsUseCase deletePurchaseTicketsUseCase, SecurityContextHelper securityContextHelper, - GetPurchaseTicketsUseCase getPurchaseTicketsUseCase) { + DeletePurchaseItemsUseCase deletePurchaseItemsUseCase, SecurityContextHelper securityContextHelper) { this.createPurchaseUseCase = createPurchaseUseCase; - this.deletePurchaseTicketsUseCase = deletePurchaseTicketsUseCase; - this.getPurchaseTicketsUseCase = getPurchaseTicketsUseCase; + this.deletePurchaseItemsUseCase = deletePurchaseItemsUseCase; this.securityContextHelper = securityContextHelper; } @@ -55,24 +48,11 @@ public ResponseEntity> postPurchase( return ResponseEntity.status(HttpStatus.CREATED).body(responseBody); } - @DeleteMapping("/{purchaseId}/tickets") - public ResponseEntity deletePurchaseTickets(@PathVariable UUID purchaseId, - @Valid @RequestBody DeletePurchaseTicketsRequestDto request) { + @DeleteMapping("/{purchaseId}/items") + public ResponseEntity deletePurchaseItems(@PathVariable UUID purchaseId, + @Valid @RequestBody DeletePurchaseItemsRequestDto request) { var memberEmail = securityContextHelper.getEmailInToken(); - deletePurchaseTicketsUseCase.deletePurchaseTickets(request.convertToDto(memberEmail, purchaseId)); + deletePurchaseItemsUseCase.deletePurchaseItems(request.convertToDto(memberEmail, purchaseId)); return ResponseEntity.status(HttpStatus.OK).build(); } - - @GetMapping("/{purchaseId}/tickets") - public ResponseEntity> getPurchaseTickets( - @PathVariable UUID purchaseId) { - - var memberEmail = securityContextHelper.getEmailInToken(); - var result = getPurchaseTicketsUseCase.getPurchaseTickets( - new GetPurchaseTicketsCommandDto(purchaseId, memberEmail)); - var responseBody = ApiResponse.wrap(GetPurchaseTicketsResponseDto.convertFromDto(result)); - - return ResponseEntity.status(HttpStatus.OK).body(responseBody); - } - } \ No newline at end of file diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/purchase/controller/dto/DeletePurchaseItemsRequestDto.java b/src/main/java/com/tiketeer/Tiketeer/domain/purchase/controller/dto/DeletePurchaseItemsRequestDto.java new file mode 100644 index 00000000..e3a4b259 --- /dev/null +++ b/src/main/java/com/tiketeer/Tiketeer/domain/purchase/controller/dto/DeletePurchaseItemsRequestDto.java @@ -0,0 +1,32 @@ +package com.tiketeer.Tiketeer.domain.purchase.controller.dto; + +import java.util.List; +import java.util.UUID; + +import com.tiketeer.Tiketeer.domain.purchase.usecase.dto.DeletePurchaseItemsCommandDto; + +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@Getter +@ToString +@NoArgsConstructor(force = true) +public class DeletePurchaseItemsRequestDto { + @NotNull + private final List purchaseItemIds; + + @Builder + public DeletePurchaseItemsRequestDto(@NotNull List purchaseItemIds) { + this.purchaseItemIds = purchaseItemIds; + } + + public DeletePurchaseItemsCommandDto convertToDto(String memberEmail, UUID purchaseId) { + return DeletePurchaseItemsCommandDto.builder().memberEmail(memberEmail) + .purchaseItemIds(purchaseItemIds) + .purchaseId(purchaseId) + .build(); + } +} diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/purchase/controller/dto/DeletePurchaseTicketsRequestDto.java b/src/main/java/com/tiketeer/Tiketeer/domain/purchase/controller/dto/DeletePurchaseTicketsRequestDto.java deleted file mode 100644 index a0c80f5d..00000000 --- a/src/main/java/com/tiketeer/Tiketeer/domain/purchase/controller/dto/DeletePurchaseTicketsRequestDto.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.tiketeer.Tiketeer.domain.purchase.controller.dto; - -import java.util.List; -import java.util.UUID; - -import com.tiketeer.Tiketeer.domain.purchase.usecase.dto.DeletePurchaseTicketsCommandDto; - -import jakarta.validation.constraints.NotNull; -import lombok.Builder; -import lombok.Getter; -import lombok.ToString; - -@Getter -@ToString -public class DeletePurchaseTicketsRequestDto { - @NotNull - private final List ticketIds; - - @NotNull - private final UUID purchaseId; - - @Builder - public DeletePurchaseTicketsRequestDto(@NotNull List ticketIds, @NotNull UUID purchaseId) { - this.ticketIds = ticketIds; - this.purchaseId = purchaseId; - } - - public DeletePurchaseTicketsCommandDto convertToDto(String memberEmail, UUID purchaseId) { - return DeletePurchaseTicketsCommandDto.builder().memberEmail(memberEmail) - .ticketIds(this.ticketIds) - .purchaseId(purchaseId) - .build(); - } -} diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/purchase/controller/dto/PostPurchaseRequestDto.java b/src/main/java/com/tiketeer/Tiketeer/domain/purchase/controller/dto/PostPurchaseRequestDto.java index 9eadc81d..532a65a7 100644 --- a/src/main/java/com/tiketeer/Tiketeer/domain/purchase/controller/dto/PostPurchaseRequestDto.java +++ b/src/main/java/com/tiketeer/Tiketeer/domain/purchase/controller/dto/PostPurchaseRequestDto.java @@ -1,9 +1,11 @@ package com.tiketeer.Tiketeer.domain.purchase.controller.dto; +import java.util.List; import java.util.UUID; import com.tiketeer.Tiketeer.domain.purchase.usecase.dto.CreatePurchaseCommandDto; +import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import lombok.Builder; import lombok.Getter; @@ -18,20 +20,20 @@ public class PostPurchaseRequestDto { private final UUID ticketingId; @NotNull - private final Integer count; + @Valid + private final List tickets; @Builder public PostPurchaseRequestDto(@NotNull UUID ticketingId, - @NotNull Integer count) { + @NotNull @Valid List tickets) { this.ticketingId = ticketingId; - this.count = count; - + this.tickets = tickets; } public CreatePurchaseCommandDto convertToDto(String memberEmail) { return CreatePurchaseCommandDto.builder().memberEmail(memberEmail) - .ticketingId(this.ticketingId) - .count(this.count) + .ticketingId(ticketingId) + .tickets(tickets) .build(); } } diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/purchase/exception/NotEnoughTicketException.java b/src/main/java/com/tiketeer/Tiketeer/domain/purchase/exception/NotEnoughTicketException.java deleted file mode 100644 index 0e9dde7b..00000000 --- a/src/main/java/com/tiketeer/Tiketeer/domain/purchase/exception/NotEnoughTicketException.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.tiketeer.Tiketeer.domain.purchase.exception; - -import com.tiketeer.Tiketeer.exception.DefinedException; -import com.tiketeer.Tiketeer.exception.code.PurchaseExceptionCode; - -public class NotEnoughTicketException extends DefinedException { - public NotEnoughTicketException() { - super(PurchaseExceptionCode.NOT_ENOUGH_TICKET); - } -} diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/purchase/repository/PurchaseItemRepository.java b/src/main/java/com/tiketeer/Tiketeer/domain/purchase/repository/PurchaseItemRepository.java new file mode 100644 index 00000000..78cffb9c --- /dev/null +++ b/src/main/java/com/tiketeer/Tiketeer/domain/purchase/repository/PurchaseItemRepository.java @@ -0,0 +1,15 @@ +package com.tiketeer.Tiketeer.domain.purchase.repository; + +import java.util.List; +import java.util.UUID; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import com.tiketeer.Tiketeer.domain.purchase.Purchase; +import com.tiketeer.Tiketeer.domain.purchase.PurchaseItem; + +@Repository +public interface PurchaseItemRepository extends JpaRepository { + List findAllByPurchase(Purchase purchase); +} diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/purchase/repository/PurchaseRepository.java b/src/main/java/com/tiketeer/Tiketeer/domain/purchase/repository/PurchaseRepository.java index 79e7cff2..0992dca2 100644 --- a/src/main/java/com/tiketeer/Tiketeer/domain/purchase/repository/PurchaseRepository.java +++ b/src/main/java/com/tiketeer/Tiketeer/domain/purchase/repository/PurchaseRepository.java @@ -9,15 +9,16 @@ import org.springframework.stereotype.Repository; import com.tiketeer.Tiketeer.domain.member.Member; -import com.tiketeer.Tiketeer.domain.member.usecase.dto.GetMemberPurchasesResultDto; +import com.tiketeer.Tiketeer.domain.member.usecase.dto.GetMemberPurchase; import com.tiketeer.Tiketeer.domain.purchase.Purchase; @Repository public interface PurchaseRepository extends JpaRepository { @Query( - "SELECT new com.tiketeer.Tiketeer.domain.member.usecase.dto.GetMemberPurchasesResultDto(p.id, tg.id, tg.title, tg.location, tg.eventTime, tg.saleStart, tg.saleEnd, p.createdAt, tg.category, tg.price, count(*)) " - + "FROM Purchase p LEFT JOIN Ticket t ON p = t.purchase LEFT JOIN Ticketing tg ON t.ticketing = tg " - + "WHERE p.member = :member GROUP BY p, tg ORDER BY p.createdAt, p.id" + value = "SELECT p " + + "FROM Purchase p LEFT JOIN fetch p.purchaseItems pi LEFT JOIN fetch pi.ticket t LEFT JOIN fetch t.ticketing tg " + + "WHERE p.member = :member ORDER BY p.createdAt, p.id" ) - List findWithTicketingByMember(@Param("member") Member member); + List findWithTicketingByMember(@Param("member") Member member); + } diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/purchase/service/PurchaseService.java b/src/main/java/com/tiketeer/Tiketeer/domain/purchase/service/PurchaseService.java index 7e903c93..f3f63101 100644 --- a/src/main/java/com/tiketeer/Tiketeer/domain/purchase/service/PurchaseService.java +++ b/src/main/java/com/tiketeer/Tiketeer/domain/purchase/service/PurchaseService.java @@ -1,7 +1,6 @@ package com.tiketeer.Tiketeer.domain.purchase.service; import java.time.LocalDateTime; -import java.util.List; import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; @@ -9,26 +8,21 @@ import org.springframework.transaction.annotation.Transactional; import com.tiketeer.Tiketeer.domain.purchase.exception.AccessForNotOwnedPurchaseException; -import com.tiketeer.Tiketeer.domain.purchase.exception.EmptyPurchaseException; import com.tiketeer.Tiketeer.domain.purchase.exception.PurchaseNotFoundException; import com.tiketeer.Tiketeer.domain.purchase.exception.PurchaseNotInSalePeriodException; import com.tiketeer.Tiketeer.domain.purchase.repository.PurchaseRepository; -import com.tiketeer.Tiketeer.domain.ticket.Ticket; -import com.tiketeer.Tiketeer.domain.ticket.repository.TicketRepository; import com.tiketeer.Tiketeer.domain.ticketing.exception.TicketingNotFoundException; import com.tiketeer.Tiketeer.domain.ticketing.repository.TicketingRepository; @Service @Transactional(readOnly = true) public class PurchaseService { - private final TicketRepository ticketRepository; private final TicketingRepository ticketingRepository; private final PurchaseRepository purchaseRepository; @Autowired - PurchaseService(TicketRepository ticketRepository, TicketingRepository ticketingRepository, + PurchaseService(TicketingRepository ticketingRepository, PurchaseRepository purchaseRepository) { - this.ticketRepository = ticketRepository; this.ticketingRepository = ticketingRepository; this.purchaseRepository = purchaseRepository; } @@ -49,13 +43,4 @@ public void validatePurchaseOwnership(UUID purchaseId, String email) { throw new AccessForNotOwnedPurchaseException(); } } - - public List findTicketsUnderPurchase(UUID purchaseId) { - var purchase = purchaseRepository.findById(purchaseId).orElseThrow(PurchaseNotFoundException::new); - var ticketsUnderPurchase = ticketRepository.findAllByPurchase(purchase); - if (ticketsUnderPurchase.isEmpty()) { - throw new EmptyPurchaseException(); - } - return ticketsUnderPurchase; - } } \ No newline at end of file diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/CreatePurchaseUseCase.java b/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/CreatePurchaseUseCase.java index cf57700e..868ace17 100644 --- a/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/CreatePurchaseUseCase.java +++ b/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/CreatePurchaseUseCase.java @@ -3,8 +3,6 @@ import java.util.List; import java.util.UUID; -import org.springframework.data.domain.Limit; - import com.tiketeer.Tiketeer.domain.purchase.usecase.dto.CreatePurchaseCommandDto; import com.tiketeer.Tiketeer.domain.purchase.usecase.dto.CreatePurchaseResultDto; import com.tiketeer.Tiketeer.domain.ticket.Ticket; @@ -19,6 +17,6 @@ default CreatePurchaseResultDto createPurchase(CreatePurchaseCommandDto command, @FunctionalInterface interface ListTicketStrategy { - List findByTicketingIdAndPurchaseIsNullOrderById(UUID ticketingId, Limit limit); + List findAllById(List ticketIds); } } diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/CreatePurchaseUseCaseCore.java b/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/CreatePurchaseUseCaseCore.java index e6d10e56..2ecdaa14 100644 --- a/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/CreatePurchaseUseCaseCore.java +++ b/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/CreatePurchaseUseCaseCore.java @@ -1,51 +1,61 @@ package com.tiketeer.Tiketeer.domain.purchase.usecase; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Stream; + import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Limit; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.tiketeer.Tiketeer.domain.member.service.MemberCrudService; import com.tiketeer.Tiketeer.domain.member.service.MemberPointService; import com.tiketeer.Tiketeer.domain.purchase.Purchase; -import com.tiketeer.Tiketeer.domain.purchase.exception.NotEnoughTicketException; +import com.tiketeer.Tiketeer.domain.purchase.PurchaseItem; +import com.tiketeer.Tiketeer.domain.purchase.repository.PurchaseItemRepository; import com.tiketeer.Tiketeer.domain.purchase.repository.PurchaseRepository; import com.tiketeer.Tiketeer.domain.purchase.service.PurchaseService; import com.tiketeer.Tiketeer.domain.purchase.usecase.dto.CreatePurchaseCommandDto; import com.tiketeer.Tiketeer.domain.purchase.usecase.dto.CreatePurchaseResultDto; +import com.tiketeer.Tiketeer.domain.ticket.Ticket; +import com.tiketeer.Tiketeer.domain.ticket.exception.TicketNotFoundException; import com.tiketeer.Tiketeer.domain.ticket.repository.TicketRepository; -import com.tiketeer.Tiketeer.domain.ticketing.service.TicketingService; +import com.tiketeer.Tiketeer.domain.ticket.service.TicketStockService; @Service public class CreatePurchaseUseCaseCore implements CreatePurchaseUseCase { private final PurchaseRepository purchaseRepository; - private final TicketingService ticketingService; + private final PurchaseItemRepository purchaseItemRepository; private final PurchaseService purchaseService; private final MemberPointService memberPointService; private final MemberCrudService memberCrudService; private final TicketRepository ticketRepository; + private final TicketStockService ticketStockService; @Autowired public CreatePurchaseUseCaseCore( PurchaseRepository purchaseRepository, - TicketingService ticketingService, + PurchaseItemRepository purchaseItemRepository, PurchaseService purchaseService, MemberPointService memberPointService, MemberCrudService memberCrudService, - TicketRepository ticketRepository + TicketRepository ticketRepository, + TicketStockService ticketStockService ) { this.purchaseRepository = purchaseRepository; - this.ticketingService = ticketingService; + this.purchaseItemRepository = purchaseItemRepository; this.purchaseService = purchaseService; this.memberPointService = memberPointService; this.memberCrudService = memberCrudService; this.ticketRepository = ticketRepository; + this.ticketStockService = ticketStockService; } @Override @Transactional public CreatePurchaseResultDto createPurchase(CreatePurchaseCommandDto command) { - return execPurchaseLogic(command, ticketRepository::findByTicketingIdAndPurchaseIsNullOrderById); + return execPurchaseLogic(command, ticketRepository::findAllById); } @Override @@ -58,27 +68,38 @@ public CreatePurchaseResultDto createPurchase(CreatePurchaseCommandDto command, private CreatePurchaseResultDto execPurchaseLogic(CreatePurchaseCommandDto command, ListTicketStrategy listTicketStrategy) { var ticketingId = command.getTicketingId(); - var count = command.getCount(); var member = memberCrudService.findByEmail(command.getMemberEmail()); purchaseService.validateTicketingSalePeriod(ticketingId, command.getCommandCreatedAt()); - var ticketing = ticketingService.findById(ticketingId); - - memberPointService.subtractPoint(member.getId(), ticketing.getPrice() * count); - var purchase = purchaseRepository.save(Purchase.builder().member(member).build()); - var tickets = listTicketStrategy.findByTicketingIdAndPurchaseIsNullOrderById(ticketingId, Limit.of(count)); + var ticketIds = command.getTickets().stream().map(CreatePurchaseCommandDto.Ticket::getId).toList(); + var tickets = listTicketStrategy.findAllById(ticketIds); + + if (tickets.isEmpty()) { + throw new TicketNotFoundException(); + } + + Map ticketMap = new HashMap<>(tickets.size()); + for (var t : tickets) { + ticketMap.put(t.getId(), t); + } - if (tickets.size() < count) { - throw new NotEnoughTicketException(); + for (var t : command.getTickets()) { + var ticket = ticketMap.get(t.getId()); + var purchaseCount = t.getCount(); + ticketStockService.subtractRemainingStock(t.getId(), purchaseCount); + var purchaseItems = Stream.generate(() -> new PurchaseItem(purchase, ticket)).limit(purchaseCount).toList(); + purchaseItemRepository.saveAll(purchaseItems); } - tickets.forEach(ticket -> { - ticket.setPurchase(purchase); - }); + var totalCost = command.getTickets() + .stream() + .mapToLong(t -> ticketMap.get(t.getId()).getPrice() * t.getCount()) + .sum(); + memberPointService.subtractPoint(member.getId(), totalCost); return CreatePurchaseResultDto.builder() .purchaseId(purchase.getId()) diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/DeletePurchaseItemsUseCase.java b/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/DeletePurchaseItemsUseCase.java new file mode 100644 index 00000000..eb8bf263 --- /dev/null +++ b/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/DeletePurchaseItemsUseCase.java @@ -0,0 +1,73 @@ +package com.tiketeer.Tiketeer.domain.purchase.usecase; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.tiketeer.Tiketeer.domain.member.service.MemberCrudService; +import com.tiketeer.Tiketeer.domain.member.service.MemberPointService; +import com.tiketeer.Tiketeer.domain.purchase.exception.PurchaseNotFoundException; +import com.tiketeer.Tiketeer.domain.purchase.repository.PurchaseItemRepository; +import com.tiketeer.Tiketeer.domain.purchase.repository.PurchaseRepository; +import com.tiketeer.Tiketeer.domain.purchase.service.PurchaseService; +import com.tiketeer.Tiketeer.domain.purchase.usecase.dto.DeletePurchaseItemsCommandDto; +import com.tiketeer.Tiketeer.domain.ticket.repository.TicketRepository; +import com.tiketeer.Tiketeer.domain.ticket.service.TicketStockService; + +@Service +public class DeletePurchaseItemsUseCase { + private final PurchaseRepository purchaseRepository; + private final PurchaseItemRepository purchaseItemRepository; + private final TicketRepository ticketRepository; + private final PurchaseService purchaseService; + private final MemberPointService memberPointService; + private final MemberCrudService memberCrudService; + private final TicketStockService ticketStockService; + + @Autowired + DeletePurchaseItemsUseCase(PurchaseRepository purchaseRepository, PurchaseItemRepository purchaseItemRepository, + TicketRepository ticketRepository, PurchaseService purchaseService, MemberPointService memberPointService, + MemberCrudService memberCrudService, TicketStockService ticketStockService) { + this.purchaseRepository = purchaseRepository; + this.purchaseItemRepository = purchaseItemRepository; + this.ticketRepository = ticketRepository; + this.purchaseService = purchaseService; + this.memberPointService = memberPointService; + this.memberCrudService = memberCrudService; + this.ticketStockService = ticketStockService; + } + + @Transactional + public void deletePurchaseItems(DeletePurchaseItemsCommandDto command) { + var purchase = purchaseRepository.findById(command.getPurchaseId()).orElseThrow( + PurchaseNotFoundException::new); + purchaseService.validatePurchaseOwnership(purchase.getId(), command.getMemberEmail()); + + var purchaseItemsToRefund = purchaseItemRepository.findAllById(command.getPurchaseItemIds()); + + purchaseService.validateTicketingSalePeriod( + purchaseItemsToRefund.getFirst().getTicket().getTicketing().getId(), LocalDateTime.now()); + + Map ticketMap = new HashMap<>(command.getPurchaseItemIds().size()); + for (var pi : purchaseItemsToRefund) { + ticketMap.merge(pi.getTicket().getId(), 1, Integer::sum); + } + for (Map.Entry entry : ticketMap.entrySet()) { + var ticketId = entry.getKey(); + var count = entry.getValue(); + ticketStockService.addRemainingStock(ticketId, count); + } + purchaseItemRepository.deleteAllInBatch(purchaseItemsToRefund); + if (purchaseItemRepository.findAllByPurchase(purchase).isEmpty()) { + purchaseRepository.delete(purchase); + } + var member = memberCrudService.findByEmail(command.getMemberEmail()); + var totalCost = purchaseItemsToRefund.stream().mapToLong(x -> x.getTicket().getPrice()).sum(); + memberPointService.addPoint(member.getId(), totalCost); + } +} diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/DeletePurchaseTicketsUseCase.java b/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/DeletePurchaseTicketsUseCase.java deleted file mode 100644 index 227ea027..00000000 --- a/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/DeletePurchaseTicketsUseCase.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.tiketeer.Tiketeer.domain.purchase.usecase; - -import java.util.concurrent.atomic.AtomicInteger; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import com.tiketeer.Tiketeer.domain.member.service.MemberCrudService; -import com.tiketeer.Tiketeer.domain.member.service.MemberPointService; -import com.tiketeer.Tiketeer.domain.purchase.exception.PurchaseNotFoundException; -import com.tiketeer.Tiketeer.domain.purchase.repository.PurchaseRepository; -import com.tiketeer.Tiketeer.domain.purchase.service.PurchaseService; -import com.tiketeer.Tiketeer.domain.purchase.usecase.dto.DeletePurchaseTicketsCommandDto; -import com.tiketeer.Tiketeer.domain.ticket.Ticket; -import com.tiketeer.Tiketeer.domain.ticket.repository.TicketRepository; - -@Service -public class DeletePurchaseTicketsUseCase { - private final PurchaseRepository purchaseRepository; - private final TicketRepository ticketRepository; - private final PurchaseService purchaseService; - private final MemberPointService memberPointService; - private final MemberCrudService memberCrudService; - - @Autowired - DeletePurchaseTicketsUseCase(PurchaseRepository purchaseRepository, - TicketRepository ticketRepository, PurchaseService purchaseService, MemberPointService memberPointService, - MemberCrudService memberCrudService) { - this.purchaseRepository = purchaseRepository; - this.ticketRepository = ticketRepository; - this.purchaseService = purchaseService; - this.memberPointService = memberPointService; - this.memberCrudService = memberCrudService; - } - - @Transactional - public void deletePurchaseTickets(DeletePurchaseTicketsCommandDto command) { - var purchase = purchaseRepository.findById(command.getPurchaseId()).orElseThrow( - PurchaseNotFoundException::new); - var ticketsUnderPurchase = purchaseService.findTicketsUnderPurchase(purchase.getId()); - var ticketsToRefund = ticketRepository.findAllById(command.getTicketIds()); - var ticketing = ticketsUnderPurchase.getFirst().getTicketing(); - - purchaseService.validatePurchaseOwnership(purchase.getId(), command.getMemberEmail()); - purchaseService.validateTicketingSalePeriod(ticketing.getId(), command.getCommandCreatedAt()); - - var ticketIdUnderPurchase = ticketsUnderPurchase.stream().map(Ticket::getId).toList(); - AtomicInteger numOfDeletedTicket = new AtomicInteger(); - ticketsToRefund.forEach(ticket -> { - if (ticketIdUnderPurchase.contains(ticket.getId())) { - ticket.setPurchase(null); - numOfDeletedTicket.getAndIncrement(); - } - }); - if (numOfDeletedTicket.get() == ticketsUnderPurchase.size()) { - purchaseRepository.delete(purchase); - } - - var member = memberCrudService.findByEmail(command.getMemberEmail()); - memberPointService.addPoint(member.getId(), ticketing.getPrice() * numOfDeletedTicket.get()); - } -} diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/GetPurchaseTicketsUseCase.java b/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/GetPurchaseTicketsUseCase.java deleted file mode 100644 index d7d01c57..00000000 --- a/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/GetPurchaseTicketsUseCase.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.tiketeer.Tiketeer.domain.purchase.usecase; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import com.tiketeer.Tiketeer.domain.purchase.exception.PurchaseNotFoundException; -import com.tiketeer.Tiketeer.domain.purchase.repository.PurchaseRepository; -import com.tiketeer.Tiketeer.domain.purchase.service.PurchaseService; -import com.tiketeer.Tiketeer.domain.purchase.usecase.dto.GetPurchaseTicketsCommandDto; -import com.tiketeer.Tiketeer.domain.purchase.usecase.dto.GetPurchaseTicketsResultDto; -import com.tiketeer.Tiketeer.domain.ticket.Ticket; -import com.tiketeer.Tiketeer.domain.ticket.repository.TicketRepository; - -@Service -public class GetPurchaseTicketsUseCase { - private final PurchaseRepository purchaseRepository; - private final TicketRepository ticketRepository; - private final PurchaseService purchaseService; - - @Autowired - GetPurchaseTicketsUseCase(PurchaseRepository purchaseRepository, - TicketRepository ticketRepository, PurchaseService purchaseService) { - this.purchaseRepository = purchaseRepository; - this.ticketRepository = ticketRepository; - this.purchaseService = purchaseService; - } - - public GetPurchaseTicketsResultDto getPurchaseTickets(GetPurchaseTicketsCommandDto command) { - - purchaseService.validatePurchaseOwnership(command.getPurchaseId(), command.getMemberEmail()); - - var purchase = purchaseRepository.findById(command.getPurchaseId()).orElseThrow(PurchaseNotFoundException::new); - var tickets = ticketRepository.findAllByPurchase(purchase); - - return GetPurchaseTicketsResultDto.builder() - .ticketIds(tickets.stream().map(Ticket::getId).toList()) - .build(); - } - -} diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/decorator/CreatePurchaseUseCaseOLock.java b/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/decorator/CreatePurchaseUseCaseOLock.java index 4f2100e5..56cc4280 100644 --- a/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/decorator/CreatePurchaseUseCaseOLock.java +++ b/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/decorator/CreatePurchaseUseCaseOLock.java @@ -31,6 +31,6 @@ public CreatePurchaseUseCaseOLock(CreatePurchaseUseCase createPurchaseUseCase, ) public CreatePurchaseResultDto createPurchase(CreatePurchaseCommandDto command) { return innerUseCase.createPurchase(command, - ticketRepository::findByTicketingIdAndPurchaseIsNullOrderByIdWithOptimisticLock); + ticketRepository::findAllByIdWithOptimisticLock); } } diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/decorator/CreatePurchaseUseCasePLock.java b/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/decorator/CreatePurchaseUseCasePLock.java index 98238687..8888145c 100644 --- a/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/decorator/CreatePurchaseUseCasePLock.java +++ b/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/decorator/CreatePurchaseUseCasePLock.java @@ -17,6 +17,6 @@ public CreatePurchaseUseCasePLock(CreatePurchaseUseCase createPurchaseUseCase, T @Override public CreatePurchaseResultDto createPurchase(CreatePurchaseCommandDto command) { return innerUseCase.createPurchase(command, - ticketRepository::findByTicketingIdAndPurchaseIsNullOrderByIdWithPessimisticLock); + ticketRepository::findAllByIdWithPessimisticLock); } } diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/dto/CreatePurchaseCommandDto.java b/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/dto/CreatePurchaseCommandDto.java index cf1668fb..dcb67b8d 100644 --- a/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/dto/CreatePurchaseCommandDto.java +++ b/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/dto/CreatePurchaseCommandDto.java @@ -1,6 +1,7 @@ package com.tiketeer.Tiketeer.domain.purchase.usecase.dto; import java.time.LocalDateTime; +import java.util.List; import java.util.UUID; import lombok.Builder; @@ -12,17 +13,30 @@ public class CreatePurchaseCommandDto { private final String memberEmail; private final UUID ticketingId; - private final Integer count; + private final List tickets; private LocalDateTime commandCreatedAt = LocalDateTime.now(); @Builder - public CreatePurchaseCommandDto(String memberEmail, UUID ticketingId, Integer count, + public CreatePurchaseCommandDto(String memberEmail, UUID ticketingId, List tickets, LocalDateTime commandCreatedAt) { this.memberEmail = memberEmail; this.ticketingId = ticketingId; - this.count = count; + this.tickets = tickets; if (commandCreatedAt != null) { this.commandCreatedAt = commandCreatedAt; } } + + @Getter + @ToString + public static class Ticket { + private final UUID id; + private final Integer count; + + @Builder + public Ticket(UUID id, Integer count) { + this.id = id; + this.count = count; + } + } } diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/dto/DeletePurchaseTicketsCommandDto.java b/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/dto/DeletePurchaseItemsCommandDto.java similarity index 72% rename from src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/dto/DeletePurchaseTicketsCommandDto.java rename to src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/dto/DeletePurchaseItemsCommandDto.java index 51f56c10..f5922b6b 100644 --- a/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/dto/DeletePurchaseTicketsCommandDto.java +++ b/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/dto/DeletePurchaseItemsCommandDto.java @@ -10,18 +10,18 @@ @Getter @ToString -public class DeletePurchaseTicketsCommandDto { +public class DeletePurchaseItemsCommandDto { private final String memberEmail; - private final List ticketIds; + private final List purchaseItemIds; private final UUID purchaseId; private LocalDateTime commandCreatedAt = LocalDateTime.now(); @Builder - public DeletePurchaseTicketsCommandDto(String memberEmail, List ticketIds, + public DeletePurchaseItemsCommandDto(String memberEmail, List purchaseItemIds, UUID purchaseId, LocalDateTime commandCreatedAt) { this.memberEmail = memberEmail; - this.ticketIds = ticketIds; + this.purchaseItemIds = purchaseItemIds; this.purchaseId = purchaseId; if (commandCreatedAt != null) { this.commandCreatedAt = commandCreatedAt; diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/dto/GetPurchaseTicketsResponseDto.java b/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/dto/GetPurchaseTicketsResponseDto.java deleted file mode 100644 index f15ca963..00000000 --- a/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/dto/GetPurchaseTicketsResponseDto.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.tiketeer.Tiketeer.domain.purchase.usecase.dto; - -import java.util.List; -import java.util.UUID; - -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.ToString; - -@Getter -@ToString -@NoArgsConstructor(force = true) -public class GetPurchaseTicketsResponseDto { - - private final List ticketIds; - - @Builder - public GetPurchaseTicketsResponseDto(List ticketIds) { - this.ticketIds = ticketIds; - } - - public static GetPurchaseTicketsResponseDto convertFromDto(GetPurchaseTicketsResultDto dto) { - return GetPurchaseTicketsResponseDto.builder() - .ticketIds(dto.getTicketIds()) - .build(); - } -} diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/dto/GetPurchaseTicketsResultDto.java b/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/dto/GetPurchaseTicketsResultDto.java deleted file mode 100644 index 86f70442..00000000 --- a/src/main/java/com/tiketeer/Tiketeer/domain/purchase/usecase/dto/GetPurchaseTicketsResultDto.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.tiketeer.Tiketeer.domain.purchase.usecase.dto; - -import java.util.List; -import java.util.UUID; - -import lombok.Builder; -import lombok.Getter; -import lombok.ToString; - -@Getter -@ToString -public class GetPurchaseTicketsResultDto { - - private final List ticketIds; - - @Builder - public GetPurchaseTicketsResultDto(List ticketIds) { - this.ticketIds = ticketIds; - } -} diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/ticket/Ticket.java b/src/main/java/com/tiketeer/Tiketeer/domain/ticket/Ticket.java index a4c82be3..e1d4f29c 100644 --- a/src/main/java/com/tiketeer/Tiketeer/domain/ticket/Ticket.java +++ b/src/main/java/com/tiketeer/Tiketeer/domain/ticket/Ticket.java @@ -7,7 +7,6 @@ import org.springframework.data.annotation.CreatedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; -import com.tiketeer.Tiketeer.domain.purchase.Purchase; import com.tiketeer.Tiketeer.domain.ticketing.Ticketing; import jakarta.persistence.Column; @@ -41,10 +40,25 @@ public class Ticket { @Column(name = "ticket_id", nullable = false, updatable = false) private UUID id; - @ManyToOne - @JoinColumn(name = "purchase_id", foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT)) @Setter - private Purchase purchase; + @Column(name = "price", nullable = false) + private long price; + + @Setter + @Column(name = "stock", nullable = false) + private Integer stock; + + @Setter + @Column(name = "remaining_stock", nullable = false) + private Integer remainingStock; + + @Setter + @Column(name = "title", nullable = false) + private String title; + + @Setter + @Column(name = "description", columnDefinition = "TEXT") + private String description; @ManyToOne @JoinColumn(name = "ticketing_id", foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT)) @@ -59,9 +73,25 @@ public class Ticket { private Integer version; @Builder - public Ticket(Purchase purchase, Ticketing ticketing) { - this.purchase = purchase; - this.ticketing = ticketing; + public Ticket(long price, int stock, int remainingStock, String title, String description, Ticketing ticketing) { + this.price = price; + this.stock = stock; + this.remainingStock = remainingStock; + this.title = title; + this.description = description; + setTicketing(ticketing); } + public void setTicketing(Ticketing ticketing) { + if (this.ticketing != null) { + this.ticketing.getTickets().remove(this); + } + this.ticketing = ticketing; + + if (ticketing == null) { + return; + } + + this.ticketing.getTickets().add(this); + } } diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/ticket/controller/TicketController.java b/src/main/java/com/tiketeer/Tiketeer/domain/ticket/controller/TicketController.java new file mode 100644 index 00000000..4acb7852 --- /dev/null +++ b/src/main/java/com/tiketeer/Tiketeer/domain/ticket/controller/TicketController.java @@ -0,0 +1,55 @@ +package com.tiketeer.Tiketeer.domain.ticket.controller; + +import java.util.UUID; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.tiketeer.Tiketeer.auth.SecurityContextHelper; +import com.tiketeer.Tiketeer.domain.ticket.controller.dto.PostTicketRequestDto; +import com.tiketeer.Tiketeer.domain.ticket.controller.dto.PutTicketRequestDto; +import com.tiketeer.Tiketeer.domain.ticket.usecase.CreateTicketUseCase; +import com.tiketeer.Tiketeer.domain.ticket.usecase.UpdateTicketUseCase; + +import jakarta.validation.Valid; + +@RestController +@RequestMapping("/ticketings/{ticketingId}/tickets") +public class TicketController { + private final SecurityContextHelper securityContextHelper; + private final CreateTicketUseCase createTicketUseCase; + private final UpdateTicketUseCase updateTicketUseCase; + + @Autowired + public TicketController(SecurityContextHelper securityContextHelper, CreateTicketUseCase createTicketUseCase, + UpdateTicketUseCase updateTicketUseCase) { + this.securityContextHelper = securityContextHelper; + this.createTicketUseCase = createTicketUseCase; + this.updateTicketUseCase = updateTicketUseCase; + } + + @PostMapping + public ResponseEntity postTickets(@PathVariable UUID ticketingId, + @Valid @RequestBody PostTicketRequestDto request) { + var memberEmail = securityContextHelper.getEmailInToken(); + createTicketUseCase.createTicket(request.convertToDto(memberEmail, ticketingId)); + return ResponseEntity.status(HttpStatus.CREATED).build(); + + } + + @PutMapping + public ResponseEntity putTickets(@PathVariable UUID ticketingId, + @Valid @RequestBody PutTicketRequestDto request) { + var memberEmail = securityContextHelper.getEmailInToken(); + updateTicketUseCase.updateTicket(request.convertToDto(memberEmail, ticketingId)); + return ResponseEntity.ok().build(); + } + +} diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/ticket/controller/dto/PostTicketRequestDto.java b/src/main/java/com/tiketeer/Tiketeer/domain/ticket/controller/dto/PostTicketRequestDto.java new file mode 100644 index 00000000..2a30ceeb --- /dev/null +++ b/src/main/java/com/tiketeer/Tiketeer/domain/ticket/controller/dto/PostTicketRequestDto.java @@ -0,0 +1,36 @@ +package com.tiketeer.Tiketeer.domain.ticket.controller.dto; + +import java.util.List; +import java.util.UUID; + +import com.tiketeer.Tiketeer.domain.ticket.usecase.dto.CreateTicketCommandDto; +import com.tiketeer.Tiketeer.domain.ticket.usecase.dto.CreateTicketMetadata; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@Getter +@ToString +@NoArgsConstructor(force = true) +public class PostTicketRequestDto { + @NotNull + @Valid + private final List tickets; + + @Builder + public PostTicketRequestDto(@NotNull @Valid List tickets) { + this.tickets = tickets; + } + + public CreateTicketCommandDto convertToDto(String memberEmail, UUID ticketingId) { + return CreateTicketCommandDto.builder() + .memberEmail(memberEmail) + .ticketingId(ticketingId) + .createTicketMetadataList(tickets) + .build(); + } +} diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/ticket/controller/dto/PutTicketRequestDto.java b/src/main/java/com/tiketeer/Tiketeer/domain/ticket/controller/dto/PutTicketRequestDto.java new file mode 100644 index 00000000..68686fcb --- /dev/null +++ b/src/main/java/com/tiketeer/Tiketeer/domain/ticket/controller/dto/PutTicketRequestDto.java @@ -0,0 +1,36 @@ +package com.tiketeer.Tiketeer.domain.ticket.controller.dto; + +import java.util.List; +import java.util.UUID; + +import com.tiketeer.Tiketeer.domain.ticket.usecase.dto.CreateTicketMetadata; +import com.tiketeer.Tiketeer.domain.ticket.usecase.dto.UpdateTicketCommandDto; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@Getter +@ToString +@NoArgsConstructor(force = true) +public class PutTicketRequestDto { + @NotNull + @Valid + private final List tickets; + + @Builder + public PutTicketRequestDto(@NotNull @Valid List tickets) { + this.tickets = tickets; + } + + public UpdateTicketCommandDto convertToDto(String memberEmail, UUID ticketingId) { + return UpdateTicketCommandDto.builder() + .memberEmail(memberEmail) + .ticketingId(ticketingId) + .createTicketMetadataList(tickets) + .build(); + } +} diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/ticket/exception/InvalidRemainingStockException.java b/src/main/java/com/tiketeer/Tiketeer/domain/ticket/exception/InvalidRemainingStockException.java new file mode 100644 index 00000000..002bca14 --- /dev/null +++ b/src/main/java/com/tiketeer/Tiketeer/domain/ticket/exception/InvalidRemainingStockException.java @@ -0,0 +1,10 @@ +package com.tiketeer.Tiketeer.domain.ticket.exception; + +import com.tiketeer.Tiketeer.exception.DefinedException; +import com.tiketeer.Tiketeer.exception.code.TicketExceptionCode; + +public class InvalidRemainingStockException extends DefinedException { + public InvalidRemainingStockException() { + super(TicketExceptionCode.INVALID_REMAINING_STOCK); + } +} diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/ticket/exception/NotEnoughRemainingStockException.java b/src/main/java/com/tiketeer/Tiketeer/domain/ticket/exception/NotEnoughRemainingStockException.java new file mode 100644 index 00000000..02fe5e06 --- /dev/null +++ b/src/main/java/com/tiketeer/Tiketeer/domain/ticket/exception/NotEnoughRemainingStockException.java @@ -0,0 +1,10 @@ +package com.tiketeer.Tiketeer.domain.ticket.exception; + +import com.tiketeer.Tiketeer.exception.DefinedException; +import com.tiketeer.Tiketeer.exception.code.TicketExceptionCode; + +public class NotEnoughRemainingStockException extends DefinedException { + public NotEnoughRemainingStockException() { + super(TicketExceptionCode.NOT_ENOUGH_REMAINING_STOCK); + } +} diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/ticket/repository/TicketRepository.java b/src/main/java/com/tiketeer/Tiketeer/domain/ticket/repository/TicketRepository.java index cdbf3d9a..72f27172 100644 --- a/src/main/java/com/tiketeer/Tiketeer/domain/ticket/repository/TicketRepository.java +++ b/src/main/java/com/tiketeer/Tiketeer/domain/ticket/repository/TicketRepository.java @@ -9,7 +9,6 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; -import com.tiketeer.Tiketeer.domain.purchase.Purchase; import com.tiketeer.Tiketeer.domain.ticket.Ticket; import com.tiketeer.Tiketeer.domain.ticketing.Ticketing; @@ -19,19 +18,13 @@ public interface TicketRepository extends JpaRepository { List findAllByTicketing(Ticketing ticketing); - List findAllByPurchase(Purchase purchase); - - List findByTicketingIdAndPurchaseIsNull(UUID ticketingId); - - List findAllByPurchaseIsNotNull(); - - List findByTicketingIdAndPurchaseIsNullOrderById(UUID ticketingId, Limit limit); + List findByTicketingIdOrderById(UUID ticketingId, Limit limit); @Lock(LockModeType.PESSIMISTIC_WRITE) - @Query("SELECT t FROM Ticket t WHERE t.ticketing.id = :ticketingId AND t.purchase IS NULL ORDER BY t.id") - List findByTicketingIdAndPurchaseIsNullOrderByIdWithPessimisticLock(UUID ticketingId, Limit limit); + @Query("SELECT t FROM Ticket t WHERE t.id IN :ticketIds") + List findAllByIdWithPessimisticLock(List ticketIds); @Lock(LockModeType.OPTIMISTIC) - @Query("SELECT t FROM Ticket t WHERE t.ticketing.id = :ticketingId AND t.purchase IS NULL ORDER BY FUNCTION('RAND')") - List findByTicketingIdAndPurchaseIsNullOrderByIdWithOptimisticLock(UUID ticketingId, Limit limit); + @Query("SELECT t FROM Ticket t WHERE t.id IN :ticketIds") + List findAllByIdWithOptimisticLock(List ticketIds); } diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/ticket/service/TicketCrudService.java b/src/main/java/com/tiketeer/Tiketeer/domain/ticket/service/TicketCrudService.java index 91100696..acb57d78 100644 --- a/src/main/java/com/tiketeer/Tiketeer/domain/ticket/service/TicketCrudService.java +++ b/src/main/java/com/tiketeer/Tiketeer/domain/ticket/service/TicketCrudService.java @@ -1,6 +1,5 @@ package com.tiketeer.Tiketeer.domain.ticket.service; -import java.util.Arrays; import java.util.List; import java.util.UUID; @@ -10,11 +9,15 @@ import com.tiketeer.Tiketeer.domain.ticket.Ticket; import com.tiketeer.Tiketeer.domain.ticket.repository.TicketRepository; +import com.tiketeer.Tiketeer.domain.ticket.usecase.dto.CreateTicketMetadata; import com.tiketeer.Tiketeer.domain.ticketing.Ticketing; import com.tiketeer.Tiketeer.domain.ticketing.exception.TicketingNotFoundException; import com.tiketeer.Tiketeer.domain.ticketing.repository.TicketingRepository; +import lombok.extern.slf4j.Slf4j; + @Service +@Slf4j @Transactional(readOnly = true) public class TicketCrudService { private final TicketRepository ticketRepository; @@ -32,12 +35,22 @@ public List listTicketByTicketingId(UUID ticketingId) { } @Transactional - public void createTickets(UUID ticketingId, int numOfTickets) { + public List createTickets(UUID ticketingId, + List tickets) { var ticketing = findTicketingById(ticketingId); - ticketRepository.saveAll(Arrays.stream(new int[numOfTickets]) - .mapToObj(i -> Ticket.builder().ticketing(ticketing).build()) + return ticketRepository.saveAll(tickets.stream() + .map(meta -> Ticket.builder() + .ticketing(ticketing) + .price(meta.getPrice()) + .stock(meta.getStock()) + .remainingStock(meta.getRemainingStock()) + .title(meta.getTitle()) + .description(meta.getDescription()) + .build() + ) .toList()); + } @Transactional @@ -48,4 +61,14 @@ public void deleteAllByTicketIds(List ticketIds) { private Ticketing findTicketingById(UUID ticketingId) { return ticketingRepository.findById(ticketingId).orElseThrow(TicketingNotFoundException::new); } + + @Transactional + public List updateTickets(UUID ticketingId, List tickets) { + var ticketing = findTicketingById(ticketingId); + + var ticketsInDb = ticketRepository.findAllByTicketing(ticketing); + ticketRepository.deleteAllInBatch(ticketsInDb); + + return createTickets(ticketingId, tickets); + } } diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/ticket/service/TicketStockService.java b/src/main/java/com/tiketeer/Tiketeer/domain/ticket/service/TicketStockService.java new file mode 100644 index 00000000..0a36e44f --- /dev/null +++ b/src/main/java/com/tiketeer/Tiketeer/domain/ticket/service/TicketStockService.java @@ -0,0 +1,44 @@ +package com.tiketeer.Tiketeer.domain.ticket.service; + +import java.util.UUID; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.tiketeer.Tiketeer.domain.ticket.exception.InvalidRemainingStockException; +import com.tiketeer.Tiketeer.domain.ticket.exception.NotEnoughRemainingStockException; +import com.tiketeer.Tiketeer.domain.ticket.exception.TicketNotFoundException; +import com.tiketeer.Tiketeer.domain.ticket.repository.TicketRepository; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +public class TicketStockService { + private final TicketRepository ticketRepository; + + @Autowired + public TicketStockService(TicketRepository ticketRepository) { + this.ticketRepository = ticketRepository; + } + + @Transactional + public void subtractRemainingStock(UUID ticketId, int amount) { + var ticket = ticketRepository.findById(ticketId).orElseThrow(TicketNotFoundException::new); + if (ticket.getRemainingStock() < amount) { + throw new NotEnoughRemainingStockException(); + } + ticket.setRemainingStock(ticket.getRemainingStock() - amount); + } + + @Transactional + public void addRemainingStock(UUID ticketId, int amount) { + var ticket = ticketRepository.findById(ticketId).orElseThrow(TicketNotFoundException::new); + if (ticket.getStock() < ticket.getRemainingStock() + amount) { + throw new InvalidRemainingStockException(); + } + ticket.setRemainingStock(ticket.getRemainingStock() + amount); + } + +} diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/ticket/usecase/CreateTicketUseCase.java b/src/main/java/com/tiketeer/Tiketeer/domain/ticket/usecase/CreateTicketUseCase.java new file mode 100644 index 00000000..180fcbb8 --- /dev/null +++ b/src/main/java/com/tiketeer/Tiketeer/domain/ticket/usecase/CreateTicketUseCase.java @@ -0,0 +1,43 @@ +package com.tiketeer.Tiketeer.domain.ticket.usecase; + +import java.util.Objects; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.tiketeer.Tiketeer.domain.ticket.service.TicketCrudService; +import com.tiketeer.Tiketeer.domain.ticket.usecase.dto.CreateTicketCommandDto; +import com.tiketeer.Tiketeer.domain.ticketing.exception.ModifyForNotOwnedTicketingException; +import com.tiketeer.Tiketeer.domain.ticketing.exception.TicketingNotFoundException; +import com.tiketeer.Tiketeer.domain.ticketing.repository.TicketingRepository; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +public class CreateTicketUseCase { + private final TicketCrudService ticketCrudService; + private final TicketingRepository ticketingRepository; + + @Autowired + public CreateTicketUseCase(TicketCrudService ticketCrudService, + TicketingRepository ticketingRepository) { + this.ticketCrudService = ticketCrudService; + this.ticketingRepository = ticketingRepository; + } + + @Transactional + public void createTicket(CreateTicketCommandDto command) { + var email = command.getMemberEmail(); + var ticketingId = command.getTicketingId(); + var ticketMetadataList = command.getCreateTicketMetadataList(); + + var ticketing = ticketingRepository.findById(ticketingId).orElseThrow(TicketingNotFoundException::new); + if (!Objects.equals(email, ticketing.getMember().getEmail())) { + throw new ModifyForNotOwnedTicketingException(); + } + + ticketCrudService.createTickets(ticketingId, ticketMetadataList); + } +} diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/ticket/usecase/UpdateTicketUseCase.java b/src/main/java/com/tiketeer/Tiketeer/domain/ticket/usecase/UpdateTicketUseCase.java new file mode 100644 index 00000000..6584a9af --- /dev/null +++ b/src/main/java/com/tiketeer/Tiketeer/domain/ticket/usecase/UpdateTicketUseCase.java @@ -0,0 +1,43 @@ +package com.tiketeer.Tiketeer.domain.ticket.usecase; + +import java.util.Objects; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.tiketeer.Tiketeer.domain.ticket.service.TicketCrudService; +import com.tiketeer.Tiketeer.domain.ticket.usecase.dto.UpdateTicketCommandDto; +import com.tiketeer.Tiketeer.domain.ticketing.exception.ModifyForNotOwnedTicketingException; +import com.tiketeer.Tiketeer.domain.ticketing.exception.TicketingNotFoundException; +import com.tiketeer.Tiketeer.domain.ticketing.repository.TicketingRepository; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +public class UpdateTicketUseCase { + private final TicketCrudService ticketCrudService; + private final TicketingRepository ticketingRepository; + + @Autowired + public UpdateTicketUseCase(TicketCrudService ticketCrudService, + TicketingRepository ticketingRepository) { + this.ticketCrudService = ticketCrudService; + this.ticketingRepository = ticketingRepository; + } + + @Transactional + public void updateTicket(UpdateTicketCommandDto command) { + var email = command.getMemberEmail(); + var ticketingId = command.getTicketingId(); + var ticketMetadataList = command.getCreateTicketMetadataList(); + + var ticketing = ticketingRepository.findById(ticketingId).orElseThrow(TicketingNotFoundException::new); + if (!Objects.equals(email, ticketing.getMember().getEmail())) { + throw new ModifyForNotOwnedTicketingException(); + } + + ticketCrudService.updateTickets(ticketingId, ticketMetadataList); + } +} diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/ticket/usecase/dto/CreateTicketCommandDto.java b/src/main/java/com/tiketeer/Tiketeer/domain/ticket/usecase/dto/CreateTicketCommandDto.java index 683479ba..9cdbf4ab 100644 --- a/src/main/java/com/tiketeer/Tiketeer/domain/ticket/usecase/dto/CreateTicketCommandDto.java +++ b/src/main/java/com/tiketeer/Tiketeer/domain/ticket/usecase/dto/CreateTicketCommandDto.java @@ -1,6 +1,6 @@ package com.tiketeer.Tiketeer.domain.ticket.usecase.dto; -import java.time.LocalDateTime; +import java.util.List; import java.util.UUID; import lombok.Builder; @@ -10,16 +10,16 @@ @Getter @ToString public class CreateTicketCommandDto { + private final String memberEmail; private final UUID ticketingId; - private final int numOfTickets; - private LocalDateTime commandCreatedAt = LocalDateTime.now(); + + private final List createTicketMetadataList; @Builder - public CreateTicketCommandDto(UUID ticketingId, int numOfTickets, LocalDateTime commandCreatedAt) { + public CreateTicketCommandDto(String memberEmail, UUID ticketingId, + List createTicketMetadataList) { + this.memberEmail = memberEmail; this.ticketingId = ticketingId; - this.numOfTickets = numOfTickets; - if (commandCreatedAt != null) { - this.commandCreatedAt = commandCreatedAt; - } + this.createTicketMetadataList = createTicketMetadataList; } } diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/ticket/usecase/dto/CreateTicketMetadata.java b/src/main/java/com/tiketeer/Tiketeer/domain/ticket/usecase/dto/CreateTicketMetadata.java new file mode 100644 index 00000000..1eb284eb --- /dev/null +++ b/src/main/java/com/tiketeer/Tiketeer/domain/ticket/usecase/dto/CreateTicketMetadata.java @@ -0,0 +1,37 @@ +package com.tiketeer.Tiketeer.domain.ticket.usecase.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString +public class CreateTicketMetadata { + @NotNull + @Positive + private final int stock; + @NotNull + @Positive + private final int remainingStock; + @NotBlank + private final String title; + private final String description; + @NotNull + @Positive + private final long price; + + @Builder + public CreateTicketMetadata(@NotNull @Positive int stock, @NotNull @Positive int remainingStock, + @NotBlank String title, + String description, + @NotNull @Positive long price) { + this.stock = stock; + this.remainingStock = remainingStock; + this.title = title; + this.description = description; + this.price = price; + } +} diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/ticket/usecase/dto/UpdateTicketCommandDto.java b/src/main/java/com/tiketeer/Tiketeer/domain/ticket/usecase/dto/UpdateTicketCommandDto.java new file mode 100644 index 00000000..aad71654 --- /dev/null +++ b/src/main/java/com/tiketeer/Tiketeer/domain/ticket/usecase/dto/UpdateTicketCommandDto.java @@ -0,0 +1,25 @@ +package com.tiketeer.Tiketeer.domain.ticket.usecase.dto; + +import java.util.List; +import java.util.UUID; + +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString +public class UpdateTicketCommandDto { + private final String memberEmail; + private final UUID ticketingId; + + private final List createTicketMetadataList; + + @Builder + public UpdateTicketCommandDto(String memberEmail, UUID ticketingId, + List createTicketMetadataList) { + this.memberEmail = memberEmail; + this.ticketingId = ticketingId; + this.createTicketMetadataList = createTicketMetadataList; + } +} diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/Ticketing.java b/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/Ticketing.java index 573f37c1..22fdc74d 100644 --- a/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/Ticketing.java +++ b/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/Ticketing.java @@ -1,6 +1,8 @@ package com.tiketeer.Tiketeer.domain.ticketing; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; import org.hibernate.annotations.UuidGenerator; @@ -9,6 +11,7 @@ import com.tiketeer.Tiketeer.constant.StorageEnum; import com.tiketeer.Tiketeer.domain.member.Member; +import com.tiketeer.Tiketeer.domain.ticket.Ticket; import jakarta.persistence.Column; import jakarta.persistence.ConstraintMode; @@ -21,6 +24,7 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import jakarta.persistence.Temporal; import jakarta.persistence.TemporalType; @@ -29,24 +33,18 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import lombok.ToString; @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) @Table(name = "ticketings") @EntityListeners(AuditingEntityListener.class) @Getter -@ToString public class Ticketing { @Id @UuidGenerator @Column(name = "ticketing_id", nullable = false, updatable = false) private UUID id; - @Setter - @Column(name = "price", nullable = false) - private long price; - @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "owner_id", referencedColumnName = "member_id", foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT)) @@ -102,16 +100,18 @@ public class Ticketing { @Column(name = "created_at", nullable = false) private LocalDateTime createdAt; + @OneToMany(mappedBy = "ticketing") + private List tickets = new ArrayList<>(); + @Builder public Ticketing( - long price, Member member, + Member member, String description, String title, String location, LocalDateTime eventTime, String category, int runningMinutes, LocalDateTime saleStart, LocalDateTime saleEnd, String thumbnailPath, StorageEnum storageEnum ) { - this.price = price; this.member = member; this.description = description; this.title = title; diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/controller/dto/GetTicketingResponseDto.java b/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/controller/dto/GetTicketingResponseDto.java index cb4a6b10..86e83206 100644 --- a/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/controller/dto/GetTicketingResponseDto.java +++ b/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/controller/dto/GetTicketingResponseDto.java @@ -1,6 +1,7 @@ package com.tiketeer.Tiketeer.domain.ticketing.controller.dto; import java.time.LocalDateTime; +import java.util.List; import java.util.UUID; import com.tiketeer.Tiketeer.domain.ticketing.usecase.dto.GetTicketingResultDto; @@ -18,15 +19,13 @@ public class GetTicketingResponseDto { private final String location; private final String category; private final Integer runningMinutes; - private final Long stock; - private final Long remainStock; - private final Long price; private final LocalDateTime eventTime; private final LocalDateTime saleStart; private final LocalDateTime saleEnd; private final LocalDateTime createdAt; private final String owner; private final String thumbnailPath; + private final List tickets; @Builder public GetTicketingResponseDto(UUID ticketingId, String title, @@ -34,28 +33,24 @@ public GetTicketingResponseDto(UUID ticketingId, String title, String location, String category, Integer runningMinutes, - Long stock, - Long remainStock, - Long price, LocalDateTime eventTime, LocalDateTime saleStart, LocalDateTime saleEnd, LocalDateTime createdAt, String owner, - String thumbnailPath) { + String thumbnailPath, + List tickets) { this.ticketingId = ticketingId; this.title = title; this.description = description; this.location = location; this.category = category; this.runningMinutes = runningMinutes; - this.stock = stock; - this.remainStock = remainStock; - this.price = price; this.eventTime = eventTime; this.saleStart = saleStart; this.saleEnd = saleEnd; this.createdAt = createdAt; this.owner = owner; this.thumbnailPath = thumbnailPath; + this.tickets = tickets; } public static GetTicketingResponseDto convertFromDto(GetTicketingResultDto dto) { @@ -66,15 +61,13 @@ public static GetTicketingResponseDto convertFromDto(GetTicketingResultDto dto) .location(dto.getLocation()) .category(dto.getCategory()) .runningMinutes(dto.getRunningMinutes()) - .stock(dto.getStock()) - .remainStock(dto.getRemainStock()) - .price(dto.getPrice()) .eventTime(dto.getEventTime()) .saleStart(dto.getSaleStart()) .saleEnd(dto.getSaleEnd()) .createdAt(dto.getCreatedAt()) .owner(dto.getOwner()) .thumbnailPath(dto.getThumbnailPath()) + .tickets(dto.getTickets()) .build(); } } diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/controller/dto/PatchTicketingRequestDto.java b/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/controller/dto/PatchTicketingRequestDto.java index c935990d..533fcdf8 100644 --- a/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/controller/dto/PatchTicketingRequestDto.java +++ b/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/controller/dto/PatchTicketingRequestDto.java @@ -29,12 +29,6 @@ public class PatchTicketingRequestDto { @NotNull private final Integer runningMinutes; - @NotNull - private final Integer stock; - - @NotNull - private final Long price; - private final String thumbnailPath; @NotNull @@ -57,8 +51,6 @@ public PatchTicketingRequestDto( @NotBlank String location, @NotBlank String category, @NotNull Integer runningMinutes, - @NotNull Integer stock, - @NotNull Long price, @NotNull LocalDateTime eventTime, @NotNull LocalDateTime saleStart, @NotNull LocalDateTime saleEnd) { @@ -67,8 +59,6 @@ public PatchTicketingRequestDto( this.location = location; this.category = category; this.runningMinutes = runningMinutes; - this.stock = stock; - this.price = price; this.eventTime = eventTime; this.saleStart = saleStart; this.saleEnd = saleEnd; @@ -84,8 +74,6 @@ public UpdateTicketingCommandDto convertToDto(String ticketingId, String memberE .location(location) .category(category) .runningMinutes(runningMinutes) - .stock(stock) - .price(price) .eventTime(eventTime) .saleStart(saleStart) .saleEnd(saleEnd) diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/controller/dto/PostTicketingRequestDto.java b/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/controller/dto/PostTicketingRequestDto.java index c5666514..f5461df5 100644 --- a/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/controller/dto/PostTicketingRequestDto.java +++ b/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/controller/dto/PostTicketingRequestDto.java @@ -30,14 +30,6 @@ public class PostTicketingRequestDto { @Positive private final Integer runningMinutes; - @NotNull - @Positive - private final Integer stock; - - @NotNull - @Positive - private final Long price; - private final String thumbnailPath; @NotNull @@ -59,8 +51,7 @@ public PostTicketingRequestDto( @NotBlank String location, @NotBlank String category, @NotNull Integer runningMinutes, - @NotNull Integer stock, - @NotNull Long price, String thumbnailPath, + String thumbnailPath, @NotNull LocalDateTime eventTime, @NotNull LocalDateTime saleStart, @NotNull LocalDateTime saleEnd) { @@ -69,10 +60,8 @@ public PostTicketingRequestDto( this.location = location; this.category = category; this.runningMinutes = runningMinutes; - this.stock = stock; - this.price = price; - this.thumbnailPath = thumbnailPath; this.eventTime = eventTime; + this.thumbnailPath = thumbnailPath; this.saleStart = saleStart; this.saleEnd = saleEnd; } @@ -86,8 +75,6 @@ public CreateTicketingCommandDto convertToDto(String memberEmail) { .location(location) .category(category) .runningMinutes(runningMinutes) - .stock(stock) - .price(price) .eventTime(eventTime) .saleStart(saleStart) .saleEnd(saleEnd) diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/repository/TicketingRepository.java b/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/repository/TicketingRepository.java index d933dfa3..6edb1003 100644 --- a/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/repository/TicketingRepository.java +++ b/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/repository/TicketingRepository.java @@ -12,7 +12,7 @@ import com.tiketeer.Tiketeer.domain.member.usecase.dto.GetMemberTicketingSalesResultDto; import com.tiketeer.Tiketeer.domain.ticketing.Ticketing; import com.tiketeer.Tiketeer.domain.ticketing.usecase.dto.GetAllTicketingsResultDto; -import com.tiketeer.Tiketeer.domain.ticketing.usecase.dto.GetTicketingResultDto; +import com.tiketeer.Tiketeer.domain.ticketing.usecase.dto.GetTicketing; @Repository public interface TicketingRepository extends JpaRepository { @@ -20,9 +20,10 @@ public interface TicketingRepository extends JpaRepository { @Query(value = """ select new com.tiketeer.Tiketeer.domain.member.usecase.dto.GetMemberTicketingSalesResultDto( t.id, t.title, t.description, t.location, t.eventTime, t.saleStart, t.saleEnd, - (select count(tk) from Ticket tk where tk.ticketing = t), - (select count(tk) from Ticket tk where tk.ticketing = t and tk.purchase is null ), - t.createdAt, t.category, t.runningMinutes, t.price + (select SUM(tk.stock) from Ticket tk where tk.ticketing = t), + (select SUM(tk.remainingStock) from Ticket tk where tk.ticketing = t), + t.createdAt, t.category, t.runningMinutes, + (select SUM(tk.price) from Ticket tk JOIN PurchaseItem pi ON pi.ticket = tk where tk.ticketing = t) ) from Ticketing t where t.member.email = :email @@ -32,30 +33,21 @@ public interface TicketingRepository extends JpaRepository { @Query(value = """ SELECT new com.tiketeer.Tiketeer.domain.ticketing.usecase.dto.GetAllTicketingsResultDto( - t.id, t.title, t.location, t.category, t.runningMinutes, COUNT(*), t.price, t.eventTime, + t.id, t.title, t.location, t.category, t.runningMinutes, SUM(tk.remainingStock), MIN(tk.price), t.eventTime, t.saleStart, t.saleEnd, t.createdAt - ) - FROM Ticketing t - LEFT JOIN Ticket tk ON t = tk.ticketing - WHERE tk.purchase IS NULL + ) + FROM Ticketing t + LEFT JOIN Ticket tk ON t = tk.ticketing GROUP BY t.id """) List findAllTicketingsWithRemainStock(); @Query(value = """ - SELECT new com.tiketeer.Tiketeer.domain.ticketing.usecase.dto.GetTicketingResultDto( - t.id, t.title, t.description, t.location, t.category, t.runningMinutes, - count(*), - count(case when tk.purchase is null then 1 end), - t.price, t.eventTime, - t.saleStart, t.saleEnd, t.createdAt, t.member.email, t.thumbnailPath - ) - FROM Ticketing t - LEFT JOIN Ticket tk ON t = tk.ticketing + SELECT t + FROM Ticketing t LEFT JOIN FETCH t.tickets tk WHERE t.id = :ticketingId - GROUP BY t.id """) - GetTicketingResultDto findTicketingWithRemainStock(@Param("ticketingId") UUID ticketingId); + GetTicketing findTicketingWithRemainStock(@Param("ticketingId") UUID ticketingId); List findAllByMember(Member member); } diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/service/TicketingService.java b/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/service/TicketingService.java index fc9eddfa..60c778b0 100644 --- a/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/service/TicketingService.java +++ b/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/service/TicketingService.java @@ -8,8 +8,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import com.tiketeer.Tiketeer.domain.ticket.repository.TicketRepository; -import com.tiketeer.Tiketeer.domain.ticket.service.TicketCrudService; import com.tiketeer.Tiketeer.domain.ticketing.Ticketing; import com.tiketeer.Tiketeer.domain.ticketing.exception.EventTimeNotValidException; import com.tiketeer.Tiketeer.domain.ticketing.exception.ModifyForNotOwnedTicketingException; @@ -21,15 +19,10 @@ @Transactional(readOnly = true) public class TicketingService { private final TicketingRepository ticketingRepository; - private final TicketCrudService ticketCrudService; - private final TicketRepository ticketRepository; @Autowired - public TicketingService(TicketingRepository ticketingRepository, TicketCrudService ticketCrudService, - TicketRepository ticketRepository) { + public TicketingService(TicketingRepository ticketingRepository) { this.ticketingRepository = ticketingRepository; - this.ticketCrudService = ticketCrudService; - this.ticketRepository = ticketRepository; } public Ticketing findById(UUID ticketingId) { @@ -77,6 +70,5 @@ public void validateTicketingOwnership(Ticketing ticketing, String email) { if (!ticketing.getMember().getEmail().equals(email)) { throw new ModifyForNotOwnedTicketingException(); } - return; } } diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/service/TicketingStockService.java b/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/service/TicketingStockService.java index 511e20fd..950efc9e 100644 --- a/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/service/TicketingStockService.java +++ b/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/service/TicketingStockService.java @@ -1,5 +1,6 @@ package com.tiketeer.Tiketeer.domain.ticketing.service; +import java.util.List; import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; @@ -8,6 +9,7 @@ import com.tiketeer.Tiketeer.domain.ticket.Ticket; import com.tiketeer.Tiketeer.domain.ticket.service.TicketCrudService; +import com.tiketeer.Tiketeer.domain.ticket.usecase.dto.CreateTicketMetadata; @Service @Transactional(readOnly = true) @@ -22,22 +24,14 @@ public TicketingStockService(TicketCrudService ticketCrudService, TicketingServi } @Transactional - public void createStock(UUID ticketingId, int stock) { + public void createStock(UUID ticketingId, List tickets) { var ticketing = ticketingService.findById(ticketingId); - ticketCrudService.createTickets(ticketing.getId(), stock); + ticketCrudService.createTickets(ticketing.getId(), tickets); } @Transactional - public void updateStock(UUID ticketingId, int newStock) { - var tickets = ticketCrudService.listTicketByTicketingId(ticketingId); - - var numOfTickets = tickets.size(); - if (numOfTickets > newStock) { - dropNumOfTicketsByTicketing(ticketingId, numOfTickets - newStock); - - } else if (numOfTickets < newStock) { - createStock(ticketingId, newStock - numOfTickets); - } + public void updateStock(UUID ticketingId, List tickets) { + ticketCrudService.updateTickets(ticketingId, tickets); } @Transactional diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/CreateTicketingUseCase.java b/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/CreateTicketingUseCase.java index 1f53fa71..704a4128 100644 --- a/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/CreateTicketingUseCase.java +++ b/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/CreateTicketingUseCase.java @@ -14,7 +14,6 @@ import com.tiketeer.Tiketeer.domain.ticketing.exception.EventTimeNotValidException; import com.tiketeer.Tiketeer.domain.ticketing.exception.SaleDurationNotValidException; import com.tiketeer.Tiketeer.domain.ticketing.service.TicketingService; -import com.tiketeer.Tiketeer.domain.ticketing.service.TicketingStockService; import com.tiketeer.Tiketeer.domain.ticketing.usecase.dto.CreateTicketingCommandDto; import com.tiketeer.Tiketeer.domain.ticketing.usecase.dto.CreateTicketingResultDto; @@ -24,17 +23,15 @@ @Service public class CreateTicketingUseCase { private final TicketingService ticketingService; - private final TicketingStockService ticketingStockService; private final MemberRepository memberRepository; @Value("${custom.policy.storage}") private StorageEnum storageEnum; @Autowired - public CreateTicketingUseCase(TicketingService ticketingService, TicketingStockService ticketingStockService, + public CreateTicketingUseCase(TicketingService ticketingService, MemberRepository memberRepository) { this.ticketingService = ticketingService; - this.ticketingStockService = ticketingStockService; this.memberRepository = memberRepository; } @@ -65,7 +62,6 @@ public CreateTicketingResultDto createTicketing(CreateTicketingCommandDto comman command.getLocation()) .category(command.getCategory()) .runningMinutes(command.getRunningMinutes()) - .price(command.getPrice()) .eventTime(command.getEventTime()) .saleStart(command.getSaleStart()) .saleEnd(command.getSaleEnd()) @@ -73,8 +69,6 @@ public CreateTicketingResultDto createTicketing(CreateTicketingCommandDto comman .storageEnum(storageEnum) .build()); - ticketingStockService.createStock(ticketing.getId(), command.getStock()); - return CreateTicketingResultDto.builder() .ticketingId(ticketing.getId()) .createdAt(ticketing.getCreatedAt()) diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/GetTicketingUseCase.java b/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/GetTicketingUseCase.java index cbe77f34..f7de1f79 100644 --- a/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/GetTicketingUseCase.java +++ b/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/GetTicketingUseCase.java @@ -6,6 +6,7 @@ import com.tiketeer.Tiketeer.domain.ticket.service.TicketCrudService; import com.tiketeer.Tiketeer.domain.ticketing.repository.TicketingRepository; +import com.tiketeer.Tiketeer.domain.ticketing.usecase.dto.GetTicketing; import com.tiketeer.Tiketeer.domain.ticketing.usecase.dto.GetTicketingCommandDto; import com.tiketeer.Tiketeer.domain.ticketing.usecase.dto.GetTicketingResultDto; @@ -22,6 +23,8 @@ public GetTicketingUseCase(TicketingRepository ticketingRepository, TicketCrudSe } public GetTicketingResultDto getTicketing(GetTicketingCommandDto command) { - return ticketingRepository.findTicketingWithRemainStock(command.getTicketingId()); + GetTicketing ticketingWithRemainStock = ticketingRepository.findTicketingWithRemainStock( + command.getTicketingId()); + return new GetTicketingResultDto(ticketingWithRemainStock); } } diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/UpdateTicketingUseCase.java b/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/UpdateTicketingUseCase.java index c08ba873..7458fe8b 100644 --- a/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/UpdateTicketingUseCase.java +++ b/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/UpdateTicketingUseCase.java @@ -13,7 +13,6 @@ import com.tiketeer.Tiketeer.domain.ticketing.exception.SaleDurationNotValidException; import com.tiketeer.Tiketeer.domain.ticketing.exception.UpdateTicketingAfterSaleStartException; import com.tiketeer.Tiketeer.domain.ticketing.service.TicketingService; -import com.tiketeer.Tiketeer.domain.ticketing.service.TicketingStockService; import com.tiketeer.Tiketeer.domain.ticketing.usecase.dto.UpdateTicketingCommandDto; import lombok.extern.slf4j.Slf4j; @@ -22,15 +21,13 @@ @Service public class UpdateTicketingUseCase { private final TicketingService ticketingService; - private final TicketingStockService ticketingStockService; @Value("${custom.policy.storage}") private StorageEnum storageEnum; @Autowired - public UpdateTicketingUseCase(TicketingService ticketingService, TicketingStockService ticketingStockService) { + public UpdateTicketingUseCase(TicketingService ticketingService) { this.ticketingService = ticketingService; - this.ticketingStockService = ticketingStockService; } @Transactional @@ -61,14 +58,12 @@ public void updateTicketing(UpdateTicketingCommandDto command) { ticketing.setTitle(command.getTitle()); ticketing.setDescription(command.getDescription()); - ticketing.setPrice(command.getPrice()); ticketing.setLocation(command.getLocation()); ticketing.setEventTime(eventTime); ticketing.setSaleStart(saleStart); ticketing.setSaleEnd(saleEnd); ticketing.setCategory(command.getCategory()); ticketing.setRunningMinutes(command.getRunningMinutes()); - ticketingStockService.updateStock(ticketing.getId(), command.getStock()); } private boolean isEventTimeValid(LocalDateTime baseTime, LocalDateTime eventTime) { diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/dto/CreateTicketingCommandDto.java b/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/dto/CreateTicketingCommandDto.java index 115c76b6..33b7f383 100644 --- a/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/dto/CreateTicketingCommandDto.java +++ b/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/dto/CreateTicketingCommandDto.java @@ -15,8 +15,6 @@ public class CreateTicketingCommandDto { private final String location; private final String category; private final Integer runningMinutes; - private final Integer stock; - private final Long price; private final LocalDateTime eventTime; private final LocalDateTime saleStart; private final LocalDateTime saleEnd; @@ -31,8 +29,6 @@ public CreateTicketingCommandDto( String location, String category, Integer runningMinutes, - Integer stock, - Long price, String thumbnailPath, LocalDateTime eventTime, LocalDateTime saleStart, @@ -43,8 +39,6 @@ public CreateTicketingCommandDto( this.location = location; this.category = category; this.runningMinutes = runningMinutes; - this.stock = stock; - this.price = price; this.eventTime = eventTime; this.saleStart = saleStart; this.saleEnd = saleEnd; diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/dto/GetTicketing.java b/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/dto/GetTicketing.java new file mode 100644 index 00000000..5c6be7b2 --- /dev/null +++ b/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/dto/GetTicketing.java @@ -0,0 +1,67 @@ +package com.tiketeer.Tiketeer.domain.ticketing.usecase.dto; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +import org.springframework.beans.factory.annotation.Value; + +public interface GetTicketing { + @Value("#{target.id}") + UUID getTicketingId(); + + @Value("#{target.title}") + String getTitle(); + + @Value("#{target.description}") + String getDescription(); + + @Value("#{target.location}") + String getLocation(); + + @Value("#{target.category}") + String getCategory(); + + @Value("#{target.runningMinutes}") + Integer getRunningMinutes(); + + @Value("#{target.eventTime}") + LocalDateTime getEventTime(); + + @Value("#{target.saleStart}") + LocalDateTime getSaleStart(); + + @Value("#{target.saleEnd}") + LocalDateTime getSaleEnd(); + + @Value("#{target.createdAt}") + LocalDateTime getCreatedAt(); + + @Value("#{target.member.email}") + String getOwner(); + + @Value("#{target.thumbnailPath}") + String getThumbnailPath(); + + List getTickets(); + + interface GetTicketingTickets { + @Value("#{target.id}") + UUID getTicketId(); + + @Value("#{target.price}") + Long getPrice(); + + @Value("#{target.stock}") + Integer getStock(); + + @Value("#{target.remainingStock}") + Integer getRemainingStock(); + + @Value("#{target.title}") + String getTitle(); + + @Value("#{target.description}") + String getDescription(); + } +} diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/dto/GetTicketingResultDto.java b/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/dto/GetTicketingResultDto.java index 4fea9673..6ccbbe5d 100644 --- a/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/dto/GetTicketingResultDto.java +++ b/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/dto/GetTicketingResultDto.java @@ -1,10 +1,12 @@ package com.tiketeer.Tiketeer.domain.ticketing.usecase.dto; import java.time.LocalDateTime; +import java.util.List; import java.util.UUID; import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.ToString; @ToString @@ -16,15 +18,13 @@ public class GetTicketingResultDto { private final String location; private final String category; private final Integer runningMinutes; - private final Long stock; - private final Long remainStock; - private final Long price; private final LocalDateTime eventTime; private final LocalDateTime saleStart; private final LocalDateTime saleEnd; private final LocalDateTime createdAt; private final String owner; private final String thumbnailPath; + private final List tickets; @Builder public GetTicketingResultDto(UUID ticketingId, String title, @@ -32,27 +32,60 @@ public GetTicketingResultDto(UUID ticketingId, String title, String location, String category, Integer runningMinutes, - Long stock, - Long remainStock, - Long price, LocalDateTime eventTime, LocalDateTime saleStart, LocalDateTime saleEnd, LocalDateTime createdAt, String owner, - String thumbnailPath) { + String thumbnailPath, + List tickets) { this.ticketingId = ticketingId; this.title = title; this.description = description; this.location = location; this.category = category; this.runningMinutes = runningMinutes; - this.stock = stock; - this.remainStock = remainStock; - this.price = price; this.eventTime = eventTime; this.saleStart = saleStart; this.saleEnd = saleEnd; this.createdAt = createdAt; this.owner = owner; this.thumbnailPath = thumbnailPath; + this.tickets = tickets; + } + + public GetTicketingResultDto(GetTicketing t) { + this.ticketingId = t.getTicketingId(); + this.title = t.getTitle(); + this.description = t.getDescription(); + this.location = t.getLocation(); + this.category = t.getCategory(); + this.runningMinutes = t.getRunningMinutes(); + this.eventTime = t.getEventTime(); + this.saleStart = t.getSaleStart(); + this.saleEnd = t.getSaleEnd(); + this.createdAt = t.getCreatedAt(); + this.owner = t.getOwner(); + this.thumbnailPath = t.getThumbnailPath(); + this.tickets = t.getTickets().stream().map(TicketingTicket::new).toList(); + } + + @Getter + @ToString + @NoArgsConstructor(force = true) + public static class TicketingTicket { + private final UUID id; + private long price; + private final Integer stock; + private final Integer remainingStock; + private final String title; + private final String description; + + public TicketingTicket(GetTicketing.GetTicketingTickets gtt) { + this.id = gtt.getTicketId(); + this.price = gtt.getPrice(); + this.stock = gtt.getStock(); + this.remainingStock = gtt.getRemainingStock(); + this.title = gtt.getTitle(); + this.description = gtt.getDescription(); + } } } diff --git a/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/dto/UpdateTicketingCommandDto.java b/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/dto/UpdateTicketingCommandDto.java index 50595407..d9c1b567 100644 --- a/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/dto/UpdateTicketingCommandDto.java +++ b/src/main/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/dto/UpdateTicketingCommandDto.java @@ -17,8 +17,6 @@ public class UpdateTicketingCommandDto { private final String location; private final String category; private final Integer runningMinutes; - private final Integer stock; - private final Long price; private final LocalDateTime eventTime; private final LocalDateTime saleStart; private final LocalDateTime saleEnd; @@ -34,8 +32,6 @@ public UpdateTicketingCommandDto( String location, String category, Integer runningMinutes, - Integer stock, - Long price, LocalDateTime eventTime, LocalDateTime saleStart, LocalDateTime saleEnd, @@ -48,8 +44,6 @@ public UpdateTicketingCommandDto( this.location = location; this.category = category; this.runningMinutes = runningMinutes; - this.stock = stock; - this.price = price; this.eventTime = eventTime; this.saleStart = saleStart; this.saleEnd = saleEnd; diff --git a/src/main/java/com/tiketeer/Tiketeer/exception/code/PurchaseExceptionCode.java b/src/main/java/com/tiketeer/Tiketeer/exception/code/PurchaseExceptionCode.java index 2cee55c7..10dbe7ce 100644 --- a/src/main/java/com/tiketeer/Tiketeer/exception/code/PurchaseExceptionCode.java +++ b/src/main/java/com/tiketeer/Tiketeer/exception/code/PurchaseExceptionCode.java @@ -12,8 +12,7 @@ public enum PurchaseExceptionCode implements ExceptionCode { EMPTY_PURCHASE(HttpStatus.CONFLICT, "๊ตฌ๋งค ๋‚ด์—ญ์— ํ‹ฐ์ผ“์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค."), EMPTY_PURCHASE_TOKEN(HttpStatus.UNAUTHORIZED, "ํ—ค๋”์— ๊ตฌ๋งค ๋Œ€๊ธฐ์—ด ํ† ํฐ์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค."), PURCHASE_NOT_IN_SALE_PERIOD(HttpStatus.BAD_REQUEST, "ํ‹ฐ์ผ“ ํŒ๋งค ๊ธฐ๊ฐ„์ด ์•„๋‹™๋‹ˆ๋‹ค."), - - NOT_ENOUGH_TICKET(HttpStatus.CONFLICT, "๊ตฌ๋งค ๊ฐ€๋Šฅํ•œ ํ‹ฐ์ผ“์ด ๋ถ€์กฑํ•ฉ๋‹ˆ๋‹ค."), + ACCESS_FOR_NOT_OWNED_PURCHASE(HttpStatus.FORBIDDEN, "๋ณธ์ธ ์†Œ์œ ๊ฐ€ ์•„๋‹Œ ๊ตฌ๋งค ๋‚ด์—ญ์— ์ ‘๊ทผ ๋ถˆ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค."), PURCHASE_TOKEN_NOT_VALID(HttpStatus.UNAUTHORIZED, "๊ตฌ๋งค ๋Œ€๊ธฐ์—ด ํ† ํฐ์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค."), User_NOT_IN_ORDER(HttpStatus.FORBIDDEN, "๊ตฌ๋งค ๊ฐ€๋Šฅํ•œ ๋Œ€๊ธฐ์—ด ์ˆœ์œ„์— ์•„์ง ๋“ค์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค."), diff --git a/src/main/java/com/tiketeer/Tiketeer/exception/code/TicketExceptionCode.java b/src/main/java/com/tiketeer/Tiketeer/exception/code/TicketExceptionCode.java index 32858dd2..d91b9be1 100644 --- a/src/main/java/com/tiketeer/Tiketeer/exception/code/TicketExceptionCode.java +++ b/src/main/java/com/tiketeer/Tiketeer/exception/code/TicketExceptionCode.java @@ -8,9 +8,11 @@ @Getter @RequiredArgsConstructor public enum TicketExceptionCode implements ExceptionCode { + NOT_ENOUGH_REMAINING_STOCK(HttpStatus.BAD_REQUEST, "ํ‹ฐ์ผ“ ์žฌ๊ณ ๊ฐ€ ๋ถ€์กฑํ•ฉ๋‹ˆ๋‹ค."), + INVALID_REMAINING_STOCK(HttpStatus.BAD_REQUEST, "์œ ํšจํ•˜์ง€ ์•Š์€ ์ˆ˜๋Ÿ‰์ž…๋‹ˆ๋‹ค"), TICKET_NOT_FOUND(HttpStatus.NOT_FOUND, "์กด์žฌํ•˜์ง€ ์•Š๋Š” ํ‹ฐ์ผ“์ž…๋‹ˆ๋‹ค."), TICKET_CONCURRENCY(HttpStatus.CONFLICT, "ํ‹ฐ์ผ“ ์กฐํšŒ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค."); - + private final HttpStatus httpStatus; private final String message; } diff --git a/src/main/resources/db/migration/V2__migration.sql b/src/main/resources/db/migration/V2__migration.sql new file mode 100644 index 00000000..b433099f --- /dev/null +++ b/src/main/resources/db/migration/V2__migration.sql @@ -0,0 +1,28 @@ +ALTER TABLE ticketings +DROP COLUMN price; + +ALTER TABLE tickets +DROP COLUMN purchase_id; + +ALTER TABLE tickets +ADD COLUMN price bigint not null; + +ALTER TABLE tickets +ADD COLUMN stock INTEGER not null; + +ALTER TABLE tickets +ADD COLUMN remaining_stock INTEGER not null; + +ALTER TABLE tickets +ADD COLUMN description TEXT; + +ALTER TABLE tickets +ADD COLUMN title varchar(255) not null; + +create table purchase_items ( + created_at timestamp(6) not null, + purchase_item_id binary(16) not null, + purchase_id binary(16) not null, + ticket_id binary(16) not null, + primary key (purchase_item_id) +); \ No newline at end of file diff --git a/src/test/java/com/tiketeer/Tiketeer/configuration/AppConfig.java b/src/test/java/com/tiketeer/Tiketeer/configuration/AppConfig.java index 5f357e10..574db381 100644 --- a/src/test/java/com/tiketeer/Tiketeer/configuration/AppConfig.java +++ b/src/test/java/com/tiketeer/Tiketeer/configuration/AppConfig.java @@ -6,23 +6,25 @@ import com.tiketeer.Tiketeer.domain.member.service.MemberCrudService; import com.tiketeer.Tiketeer.domain.member.service.MemberPointService; +import com.tiketeer.Tiketeer.domain.purchase.repository.PurchaseItemRepository; import com.tiketeer.Tiketeer.domain.purchase.repository.PurchaseRepository; import com.tiketeer.Tiketeer.domain.purchase.service.PurchaseService; import com.tiketeer.Tiketeer.domain.purchase.usecase.CreatePurchaseUseCase; import com.tiketeer.Tiketeer.domain.purchase.usecase.CreatePurchaseUseCaseCore; import com.tiketeer.Tiketeer.domain.ticket.repository.TicketRepository; -import com.tiketeer.Tiketeer.domain.ticketing.service.TicketingService; +import com.tiketeer.Tiketeer.domain.ticket.service.TicketStockService; @Configuration public class AppConfig { @Bean @Profile("test") public CreatePurchaseUseCase createPurchaseUseCase(PurchaseRepository purchaseRepository, - TicketingService ticketingService, + PurchaseItemRepository purchaseItemRepository, PurchaseService purchaseService, MemberPointService memberPointService, - MemberCrudService memberCrudService, TicketRepository ticketRepository) { - return new CreatePurchaseUseCaseCore(purchaseRepository, ticketingService, purchaseService, - memberPointService, memberCrudService, ticketRepository); + MemberCrudService memberCrudService, TicketRepository ticketRepository, TicketStockService ticketStockService) { + return new CreatePurchaseUseCaseCore(purchaseRepository, purchaseItemRepository, + purchaseService, + memberPointService, memberCrudService, ticketRepository, ticketStockService); } } diff --git a/src/test/java/com/tiketeer/Tiketeer/configuration/EmbeddedRedisConfig.java b/src/test/java/com/tiketeer/Tiketeer/configuration/EmbeddedRedisConfig.java index 3f523a52..703f87cf 100644 --- a/src/test/java/com/tiketeer/Tiketeer/configuration/EmbeddedRedisConfig.java +++ b/src/test/java/com/tiketeer/Tiketeer/configuration/EmbeddedRedisConfig.java @@ -7,8 +7,9 @@ import org.redisson.api.RedissonClient; import org.redisson.config.Config; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; @@ -22,7 +23,8 @@ @Slf4j @DisplayName("Embedded Redis ์„ค์ •") -@TestConfiguration +@Configuration +@Profile("test") @EnableRedisRepositories public class EmbeddedRedisConfig { diff --git a/src/test/java/com/tiketeer/Tiketeer/domain/member/controller/MemberControllerTest.java b/src/test/java/com/tiketeer/Tiketeer/domain/member/controller/MemberControllerTest.java index a86863e2..59305224 100644 --- a/src/test/java/com/tiketeer/Tiketeer/domain/member/controller/MemberControllerTest.java +++ b/src/test/java/com/tiketeer/Tiketeer/domain/member/controller/MemberControllerTest.java @@ -8,6 +8,7 @@ import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; +import java.util.Arrays; import java.util.List; import java.util.UUID; @@ -30,12 +31,15 @@ import com.tiketeer.Tiketeer.domain.member.Member; import com.tiketeer.Tiketeer.domain.member.Otp; import com.tiketeer.Tiketeer.domain.member.controller.dto.ChargePointRequestDto; -import com.tiketeer.Tiketeer.domain.member.controller.dto.GetMemberPurchasesResponseDto; +import com.tiketeer.Tiketeer.domain.member.controller.dto.GetMemberAllPurchasesResponseDto; +import com.tiketeer.Tiketeer.domain.member.controller.dto.GetMemberPurchaseResponseDto; import com.tiketeer.Tiketeer.domain.member.controller.dto.GetMemberResponseDto; import com.tiketeer.Tiketeer.domain.member.controller.dto.GetMemberTicketingSalesResponseDto; import com.tiketeer.Tiketeer.domain.member.controller.dto.ResetPasswordRequestDto; import com.tiketeer.Tiketeer.domain.member.repository.MemberRepository; import com.tiketeer.Tiketeer.domain.purchase.Purchase; +import com.tiketeer.Tiketeer.domain.purchase.PurchaseItem; +import com.tiketeer.Tiketeer.domain.purchase.repository.PurchaseItemRepository; import com.tiketeer.Tiketeer.domain.purchase.repository.PurchaseRepository; import com.tiketeer.Tiketeer.domain.role.constant.RoleEnum; import com.tiketeer.Tiketeer.domain.ticket.Ticket; @@ -66,6 +70,9 @@ class MemberControllerTest { @Autowired private ObjectMapper objectMapper; + @Autowired + private PurchaseItemRepository purchaseItemRepository; + @BeforeEach void initDB() { testHelper.initDB(); @@ -87,7 +94,6 @@ void getMemberTicketingSalesSuccess() throws Exception { Cookie cookie = new Cookie(JwtMetadata.ACCESS_TOKEN, token); var ticketing = ticketingRepository.save( Ticketing.builder() - .price(1000) .member(member) .description("") .title("test") @@ -100,9 +106,25 @@ void getMemberTicketingSalesSuccess() throws Exception { .build() ); var purchase = purchaseRepository.save(new Purchase(member)); - ticketRepository.save(new Ticket(null, ticketing)); - ticketRepository.save(new Ticket(purchase, ticketing)); - ticketRepository.save(new Ticket(purchase, ticketing)); + + var t1= ticketRepository.save(new Ticket(1000, 10, 7, "title 1", "description 1", ticketing)); + var t2 = ticketRepository.save(new Ticket(2000, 10, 7, "title 2", "description 1", ticketing)); + var t3 = ticketRepository.save(new Ticket(3000, 10, 9, "title 3", "description 1", ticketing)); + + var t1Count = 3; + purchaseItemRepository.saveAll(Arrays.stream(new int[t1Count]) + .mapToObj(i -> new PurchaseItem(purchase, t1)) + .toList()); + var t2Count = 3; + purchaseItemRepository.saveAll(Arrays.stream(new int[t2Count]) + .mapToObj(i -> new PurchaseItem(purchase, t2)) + .toList() + ); + var t3Count = 1; + purchaseItemRepository.saveAll(Arrays.stream(new int[t3Count]) + .mapToObj(i -> new PurchaseItem(purchase, t3)) + .toList() + ); //when - then MvcResult result = mockMvc.perform(get("/api/members/" + member.getId() + "/sale") @@ -122,15 +144,15 @@ void getMemberTicketingSalesSuccess() throws Exception { var dto = apiResponse.getData().getFirst(); - assertThat(dto.getPrice()).isEqualTo(1000); + assertThat(dto.getPrice()).isEqualTo(12000); assertThat(dto.getDescription()).isEqualTo(""); assertThat(dto.getTitle()).isEqualTo("test"); assertThat(dto.getLocation()).isEqualTo("Seoul"); assertThat(dto.getEventTime()).isEqualToIgnoringNanos(now); assertThat(dto.getSaleStart()).isEqualToIgnoringNanos(now); assertThat(dto.getSaleEnd()).isEqualToIgnoringNanos(now); - assertThat(dto.getStock()).isEqualTo(3); - assertThat(dto.getRemainStock()).isEqualTo(1); + assertThat(dto.getStock()).isEqualTo(30); + assertThat(dto.getRemainStock()).isEqualTo(23); assertThat(dto.getCategory()).isEqualTo(""); assertThat(dto.getRunningMinutes()).isEqualTo(600); @@ -328,9 +350,9 @@ void getMemberSuccess() throws Exception { } @Test - @DisplayName("์ •์ƒ ์กฐ๊ฑด > ๋ฉค๋ฒ„ ๊ตฌ๋งค ๋‚ด์—ญ ์กฐํšŒ ์š”์ฒญ > ์„ฑ๊ณต") + @DisplayName("์ •์ƒ ์กฐ๊ฑด > ๋ฉค๋ฒ„ ๊ตฌ๋งค ๋‚ด์—ญ ๋ฆฌ์ŠคํŠธ ์กฐํšŒ ์š”์ฒญ > ์„ฑ๊ณต") @Transactional - void getMemberPurchasesSuccess() throws Exception { + void getMemberAllPurchasesSuccess() throws Exception { // given var now = LocalDateTime.of(2024, 1, 1, 1, 1, 1); String token = testHelper.registerAndLoginAndReturnAccessToken("user@example.com", RoleEnum.SELLER); @@ -339,7 +361,6 @@ void getMemberPurchasesSuccess() throws Exception { var ticketing1 = ticketingRepository.save( Ticketing.builder() - .price(1000) .member(member) .description("") .title("test1") @@ -353,7 +374,6 @@ void getMemberPurchasesSuccess() throws Exception { ); var ticketing2 = ticketingRepository.save( Ticketing.builder() - .price(1000) .member(member) .description("") .title("test2") @@ -367,10 +387,14 @@ void getMemberPurchasesSuccess() throws Exception { ); var purchase1 = purchaseRepository.save(new Purchase(member)); var purchase2 = purchaseRepository.save(new Purchase(member)); - ticketRepository.save(new Ticket(null, ticketing1)); - ticketRepository.save(new Ticket(purchase1, ticketing1)); - ticketRepository.save(new Ticket(purchase1, ticketing1)); - ticketRepository.save(new Ticket(purchase2, ticketing2)); + + var ticket1 = ticketRepository.save(new Ticket(1000, 1, 1, "ticket1", "", ticketing1)); + var ticket2 = ticketRepository.save(new Ticket(1000, 1, 1, "ticket2", "", ticketing1)); + var ticket3 = ticketRepository.save(new Ticket(1000, 2, 2, "ticket3", "", ticketing2)); + + purchaseItemRepository.save(new PurchaseItem(purchase1, ticket1)); + purchaseItemRepository.save(new PurchaseItem(purchase1, ticket2)); + purchaseItemRepository.save(new PurchaseItem(purchase2, ticket3)); // when var result = mockMvc.perform(get("/api/members/{memberId}/purchases", member.getId()) @@ -383,8 +407,8 @@ void getMemberPurchasesSuccess() throws Exception { //then .andExpect(status().isOk()); - ApiResponse> apiResponse = testHelper.getDeserializedListApiResponse( - result.andReturn().getResponse().getContentAsString(), GetMemberPurchasesResponseDto.class); + ApiResponse> apiResponse = testHelper.getDeserializedListApiResponse( + result.andReturn().getResponse().getContentAsString(), GetMemberAllPurchasesResponseDto.class); var purchases = apiResponse.getData(); var findPurchase1 = purchases.stream() @@ -399,8 +423,76 @@ void getMemberPurchasesSuccess() throws Exception { Assertions.assertThat(findPurchase2.size()).isEqualTo(1); Assertions.assertThat(findPurchase1.getFirst().getTitle()).isEqualTo(ticketing1.getTitle()); Assertions.assertThat(findPurchase2.getFirst().getTitle()).isEqualTo(ticketing2.getTitle()); - Assertions.assertThat(findPurchase1.getFirst().getCount()).isEqualTo(2); - Assertions.assertThat(findPurchase2.getFirst().getCount()).isEqualTo(1); + Assertions.assertThat(findPurchase1.getFirst().getPurchaseItems().size()).isEqualTo(2); + Assertions.assertThat(findPurchase2.getFirst().getPurchaseItems().size()).isEqualTo(1); + } + + @Test + @DisplayName("์ •์ƒ ์กฐ๊ฑด > ๋ฉค๋ฒ„ ๊ตฌ๋งค ๋‚ด์—ญ ๋‹จ๊ฑด ์กฐํšŒ ์š”์ฒญ > ์„ฑ๊ณต") + @Transactional + void getMemberPurchaseSuccess() throws Exception { + // given + var now = LocalDateTime.of(2024, 1, 1, 1, 1, 1); + String token = testHelper.registerAndLoginAndReturnAccessToken("user@example.com", RoleEnum.SELLER); + Member member = memberRepository.findAll().getFirst(); + Cookie cookie = new Cookie(JwtMetadata.ACCESS_TOKEN, token); + + var ticketing1 = ticketingRepository.save( + Ticketing.builder() + .member(member) + .description("") + .title("test1") + .location("Seoul") + .eventTime(now) + .category("") + .runningMinutes(600) + .saleStart(now) + .saleEnd(now) + .build() + ); + var ticketing2 = ticketingRepository.save( + Ticketing.builder() + .member(member) + .description("") + .title("test2") + .location("Seoul") + .eventTime(now) + .category("") + .runningMinutes(600) + .saleStart(now) + .saleEnd(now) + .build() + ); + var purchase1 = purchaseRepository.save(new Purchase(member)); + var purchase2 = purchaseRepository.save(new Purchase(member)); + var ticket1 = ticketRepository.save(new Ticket(1000, 1, 1, "ticket1", "", ticketing1)); + var ticket2 = ticketRepository.save(new Ticket(1000, 1, 1, "ticket2", "", ticketing1)); + var ticket3 = ticketRepository.save(new Ticket(1000, 2, 2, "ticket3", "", ticketing2)); + + purchaseItemRepository.save(new PurchaseItem(purchase1, ticket1)); + purchaseItemRepository.save(new PurchaseItem(purchase1, ticket2)); + purchaseItemRepository.save(new PurchaseItem(purchase2, ticket3)); + + // when + var result = mockMvc.perform( + get("/api/members/{memberId}/purchases/{purchaseId}", member.getId(), purchase1.getId()) + .contextPath("/api") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding("utf-8") + .cookie(cookie) + ) + //then + .andExpect(status().isOk()); + + ApiResponse apiResponse = testHelper.getDeserializedApiResponse( + result.andReturn().getResponse().getContentAsString(), GetMemberPurchaseResponseDto.class); + + var purchase = apiResponse.getData(); + Assertions.assertThat(purchase.getPurchaseId()).isEqualTo(purchase1.getId()); + Assertions.assertThat(purchase.getTicketingId()).isEqualTo(ticketing1.getId()); + Assertions.assertThat(purchase.getTitle()).isEqualTo(ticketing1.getTitle()); + Assertions.assertThat(purchase.getPurchaseItems().size()).isEqualTo(2); } @Test @@ -452,7 +544,6 @@ void deleteMemberFailBecauseNonFulfilledEventExist() throws Exception { ticketingRepository.save(Ticketing.builder() .member(member) .category("์นดํ…Œ๊ณ ๋ฆฌ") - .price(10000) .title("์ œ๋ชฉ") .saleStart(now) .saleEnd(now.plusDays(7)) @@ -482,7 +573,6 @@ void deleteMemberSuccess() throws Exception { ticketingRepository.save(Ticketing.builder() .member(member) .category("์นดํ…Œ๊ณ ๋ฆฌ") - .price(10000) .title("์ œ๋ชฉ") .saleStart(now.minusDays(30)) .saleEnd(now.minusDays(23)) diff --git a/src/test/java/com/tiketeer/Tiketeer/domain/member/usecase/GetMemberPurchasesUseCaseTest.java b/src/test/java/com/tiketeer/Tiketeer/domain/member/usecase/GetMemberAllPurchasesUseCaseTest.java similarity index 71% rename from src/test/java/com/tiketeer/Tiketeer/domain/member/usecase/GetMemberPurchasesUseCaseTest.java rename to src/test/java/com/tiketeer/Tiketeer/domain/member/usecase/GetMemberAllPurchasesUseCaseTest.java index e41cdfcb..c25ef4c1 100644 --- a/src/test/java/com/tiketeer/Tiketeer/domain/member/usecase/GetMemberPurchasesUseCaseTest.java +++ b/src/test/java/com/tiketeer/Tiketeer/domain/member/usecase/GetMemberAllPurchasesUseCaseTest.java @@ -13,8 +13,10 @@ import org.springframework.transaction.annotation.Transactional; import com.tiketeer.Tiketeer.domain.member.exception.MemberNotFoundException; -import com.tiketeer.Tiketeer.domain.member.usecase.dto.GetMemberPurchasesCommandDto; +import com.tiketeer.Tiketeer.domain.member.usecase.dto.GetMemberAllPurchasesCommandDto; import com.tiketeer.Tiketeer.domain.purchase.Purchase; +import com.tiketeer.Tiketeer.domain.purchase.PurchaseItem; +import com.tiketeer.Tiketeer.domain.purchase.repository.PurchaseItemRepository; import com.tiketeer.Tiketeer.domain.purchase.repository.PurchaseRepository; import com.tiketeer.Tiketeer.domain.ticket.Ticket; import com.tiketeer.Tiketeer.domain.ticket.repository.TicketRepository; @@ -24,12 +26,12 @@ @Import({TestHelper.class}) @SpringBootTest -public class GetMemberPurchasesUseCaseTest { +public class GetMemberAllPurchasesUseCaseTest { @Autowired private TestHelper testHelper; @Autowired - private GetMemberPurchasesUseCase getMemberPurchasesUseCase; + private GetMemberAllPurchasesUseCase getMemberAllPurchasesUseCase; @Autowired private TicketingRepository ticketingRepository; @@ -38,6 +40,9 @@ public class GetMemberPurchasesUseCaseTest { @Autowired private TicketRepository ticketRepository; + @Autowired + private PurchaseItemRepository purchaseItemRepository; + @BeforeEach void initTable() { testHelper.initDB(); @@ -58,7 +63,6 @@ void getMemberPurchases() { var now = LocalDateTime.now(); var ticketing1 = ticketingRepository.save( Ticketing.builder() - .price(1000) .member(member) .description("") .title("test1") @@ -72,7 +76,6 @@ void getMemberPurchases() { ); var ticketing2 = ticketingRepository.save( Ticketing.builder() - .price(1000) .member(member) .description("") .title("test2") @@ -86,14 +89,18 @@ void getMemberPurchases() { ); var purchase1 = purchaseRepository.save(new Purchase(member)); var purchase2 = purchaseRepository.save(new Purchase(member)); - ticketRepository.save(new Ticket(null, ticketing1)); - ticketRepository.save(new Ticket(purchase1, ticketing1)); - ticketRepository.save(new Ticket(purchase1, ticketing1)); - ticketRepository.save(new Ticket(purchase2, ticketing2)); + var ticket1 = ticketRepository.save(new Ticket(1000, 4, 4, "ticket1", "", ticketing1)); + var ticket2 = ticketRepository.save(new Ticket(1000, 3, 3, "ticket2", "", ticketing2)); + var ticket3 = ticketRepository.save(new Ticket(1000, 3, 3, "ticket3", "", ticketing2)); + + purchaseItemRepository.save(new PurchaseItem(purchase1, ticket1)); + purchaseItemRepository.save(new PurchaseItem(purchase1, ticket1)); + purchaseItemRepository.save(new PurchaseItem(purchase2, ticket2)); + purchaseItemRepository.save(new PurchaseItem(purchase2, ticket3)); // when - var results = getMemberPurchasesUseCase.getMemberPurchases( - GetMemberPurchasesCommandDto.builder().memberEmail(mockEmail).build()); + var results = getMemberAllPurchasesUseCase.getMemberPurchases( + GetMemberAllPurchasesCommandDto.builder().memberEmail(mockEmail).build()); var findPurchase1 = results.stream() .filter(purchase -> purchase.getPurchaseId().equals(purchase1.getId())) .toList(); @@ -107,8 +114,8 @@ void getMemberPurchases() { Assertions.assertThat(findPurchase2.size()).isEqualTo(1); Assertions.assertThat(findPurchase1.getFirst().getTitle()).isEqualTo(ticketing1.getTitle()); Assertions.assertThat(findPurchase2.getFirst().getTitle()).isEqualTo(ticketing2.getTitle()); - Assertions.assertThat(findPurchase1.getFirst().getCount()).isEqualTo(2); - Assertions.assertThat(findPurchase2.getFirst().getCount()).isEqualTo(1); + Assertions.assertThat(findPurchase1.getFirst().getPurchaseItems().size()).isEqualTo(2); + Assertions.assertThat(findPurchase2.getFirst().getPurchaseItems().size()).isEqualTo(2); } @Test @@ -118,11 +125,11 @@ void getMemberPurchasesFailNotFoundMember() { // given var mockEmail = "test@test.com"; - var command = GetMemberPurchasesCommandDto.builder().memberEmail(mockEmail).build(); + var command = GetMemberAllPurchasesCommandDto.builder().memberEmail(mockEmail).build(); Assertions.assertThatThrownBy(() -> { // when - getMemberPurchasesUseCase.getMemberPurchases(command); + getMemberAllPurchasesUseCase.getMemberPurchases(command); // then }).isInstanceOf(MemberNotFoundException.class); } diff --git a/src/test/java/com/tiketeer/Tiketeer/domain/member/usecase/GetMemberPurchaseUseCaseTest.java b/src/test/java/com/tiketeer/Tiketeer/domain/member/usecase/GetMemberPurchaseUseCaseTest.java new file mode 100644 index 00000000..9036c591 --- /dev/null +++ b/src/test/java/com/tiketeer/Tiketeer/domain/member/usecase/GetMemberPurchaseUseCaseTest.java @@ -0,0 +1,143 @@ +package com.tiketeer.Tiketeer.domain.member.usecase; + +import java.time.LocalDateTime; +import java.util.UUID; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +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; +import org.springframework.context.annotation.Import; +import org.springframework.transaction.annotation.Transactional; + +import com.tiketeer.Tiketeer.domain.member.exception.MemberNotFoundException; +import com.tiketeer.Tiketeer.domain.member.usecase.dto.GetMemberPurchaseCommandDto; +import com.tiketeer.Tiketeer.domain.purchase.Purchase; +import com.tiketeer.Tiketeer.domain.purchase.PurchaseItem; +import com.tiketeer.Tiketeer.domain.purchase.repository.PurchaseItemRepository; +import com.tiketeer.Tiketeer.domain.purchase.repository.PurchaseRepository; +import com.tiketeer.Tiketeer.domain.ticket.Ticket; +import com.tiketeer.Tiketeer.domain.ticket.repository.TicketRepository; +import com.tiketeer.Tiketeer.domain.ticketing.Ticketing; +import com.tiketeer.Tiketeer.domain.ticketing.repository.TicketingRepository; +import com.tiketeer.Tiketeer.testhelper.TestHelper; + +@Import({TestHelper.class}) +@SpringBootTest +public class GetMemberPurchaseUseCaseTest { + @Autowired + private TestHelper testHelper; + + @Autowired + private GetMemberPurchaseUseCase getMemberPurchaseUseCase; + + @Autowired + private TicketingRepository ticketingRepository; + @Autowired + private PurchaseRepository purchaseRepository; + @Autowired + private TicketRepository ticketRepository; + + @Autowired + private PurchaseItemRepository purchaseItemRepository; + + @BeforeEach + void initTable() { + testHelper.initDB(); + } + + @AfterEach + void cleanTable() { + testHelper.cleanDB(); + } + + @Test + @DisplayName("์ •์ƒ ์กฐ๊ฑด > ๋ฉค๋ฒ„ ๊ตฌ๋งค ๋‚ด์—ญ ์กฐํšŒ ์š”์ฒญ > ์„ฑ๊ณต") + @Transactional + void getMemberPurchases() { + // given + var mockEmail = "test@test.com"; + var member = testHelper.createMember(mockEmail); + var now = LocalDateTime.now(); + var ticketing1 = ticketingRepository.save( + Ticketing.builder() + .member(member) + .description("") + .title("test1") + .location("Seoul") + .eventTime(now) + .category("") + .runningMinutes(600) + .saleStart(now) + .saleEnd(now) + .build() + ); + var ticketing2 = ticketingRepository.save( + Ticketing.builder() + .member(member) + .description("") + .title("test2") + .location("Seoul") + .eventTime(now) + .category("") + .runningMinutes(600) + .saleStart(now) + .saleEnd(now) + .build() + ); + var purchase1 = purchaseRepository.save(new Purchase(member)); + var purchase2 = purchaseRepository.save(new Purchase(member)); + var ticket1 = ticketRepository.save(new Ticket(1000, 4, 4, "ticket1", "", ticketing1)); + var ticket2 = ticketRepository.save(new Ticket(1000, 3, 3, "ticket2", "", ticketing2)); + var ticket3 = ticketRepository.save(new Ticket(1000, 3, 3, "ticket3", "", ticketing2)); + + var purchaseItem1 = purchaseItemRepository.save(new PurchaseItem(purchase1, ticket1)); + var purchaseItem2 = purchaseItemRepository.save(new PurchaseItem(purchase1, ticket1)); + purchaseItemRepository.save(new PurchaseItem(purchase2, ticket2)); + purchaseItemRepository.save(new PurchaseItem(purchase2, ticket3)); + + // when + var result = getMemberPurchaseUseCase.getMemberPurchases( + GetMemberPurchaseCommandDto.builder().memberEmail(mockEmail).purchaseId(purchase1.getId()).build()); + + // then + Assertions.assertThat(result.getPurchaseId()).isEqualTo(purchase1.getId()); + Assertions.assertThat(result.getTicketingId()).isEqualTo(ticketing1.getId()); + Assertions.assertThat(result.getTitle()).isEqualTo(ticketing1.getTitle()); + Assertions.assertThat(result.getLocation()).isEqualTo(ticketing1.getLocation()); + Assertions.assertThat(result.getEventTime()).isEqualTo(ticketing1.getEventTime()); + Assertions.assertThat(result.getCreatedAt()).isEqualTo(ticketing1.getCreatedAt()); + Assertions.assertThat(result.getRunningMinutes()).isEqualTo(ticketing1.getRunningMinutes()); + Assertions.assertThat(result.getCategory()).isEqualTo(ticketing1.getCategory()); + Assertions.assertThat(result.getThumbnailPath()).isEqualTo(ticketing1.getThumbnailPath()); + Assertions.assertThat(result.getPurchaseItems()) + .extracting("purchaseItemId") + .containsExactlyInAnyOrder(purchaseItem1.getId(), purchaseItem2.getId()); + Assertions.assertThat(result.getPurchaseItems()) + .extracting("description") + .containsExactlyInAnyOrder(purchaseItem1.getTicket().getDescription(), + purchaseItem2.getTicket().getDescription()); + } + + @Test + @DisplayName("๋ฉค๋ฒ„๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Œ > ๋ฉค๋ฒ„ ์กฐํšŒ > ์‹คํŒจ") + @Transactional(readOnly = true) + void getMemberPurchasesFailNotFoundMember() { + // given + var mockEmail = "test@test.com"; + + var command = GetMemberPurchaseCommandDto.builder() + .memberEmail(mockEmail) + .purchaseId(UUID.randomUUID()) + .build(); + + Assertions.assertThatThrownBy(() -> { + // when + getMemberPurchaseUseCase.getMemberPurchases(command); + // then + }).isInstanceOf(MemberNotFoundException.class); + } +} diff --git a/src/test/java/com/tiketeer/Tiketeer/domain/member/service/MemberTicketingServiceTest.java b/src/test/java/com/tiketeer/Tiketeer/domain/member/usecase/GetMemberTicketingSaleUseCaseTest.java similarity index 68% rename from src/test/java/com/tiketeer/Tiketeer/domain/member/service/MemberTicketingServiceTest.java rename to src/test/java/com/tiketeer/Tiketeer/domain/member/usecase/GetMemberTicketingSaleUseCaseTest.java index 70cc2d2e..cc42b8b5 100644 --- a/src/test/java/com/tiketeer/Tiketeer/domain/member/service/MemberTicketingServiceTest.java +++ b/src/test/java/com/tiketeer/Tiketeer/domain/member/usecase/GetMemberTicketingSaleUseCaseTest.java @@ -1,9 +1,10 @@ -package com.tiketeer.Tiketeer.domain.member.service; +package com.tiketeer.Tiketeer.domain.member.usecase; import static java.time.LocalDateTime.*; import static org.assertj.core.api.Assertions.*; import java.time.temporal.ChronoUnit; +import java.util.Arrays; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -13,9 +14,10 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Import; -import com.tiketeer.Tiketeer.domain.member.usecase.GetMemberTicketingSalesUseCase; import com.tiketeer.Tiketeer.domain.member.usecase.dto.GetMemberTicketingSalesCommandDto; import com.tiketeer.Tiketeer.domain.purchase.Purchase; +import com.tiketeer.Tiketeer.domain.purchase.PurchaseItem; +import com.tiketeer.Tiketeer.domain.purchase.repository.PurchaseItemRepository; import com.tiketeer.Tiketeer.domain.purchase.repository.PurchaseRepository; import com.tiketeer.Tiketeer.domain.ticket.Ticket; import com.tiketeer.Tiketeer.domain.ticket.repository.TicketRepository; @@ -25,7 +27,7 @@ @Import({TestHelper.class}) @SpringBootTest -class MemberTicketingServiceTest { +class GetMemberTicketingSaleUseCaseTest { @Autowired private TestHelper testHelper; @@ -37,6 +39,8 @@ class MemberTicketingServiceTest { private TicketRepository ticketRepository; @Autowired private GetMemberTicketingSalesUseCase getMemberTicketingSalesUseCase; + @Autowired + private PurchaseItemRepository purchaseItemRepository; @BeforeEach void initTable() { @@ -57,7 +61,6 @@ void getMemberTicketingSalesSuccess() { var member = testHelper.createMember("user@example.com"); var ticketing = ticketingRepository.save( Ticketing.builder() - .price(1000) .member(member) .description("") .title("test") @@ -70,9 +73,24 @@ void getMemberTicketingSalesSuccess() { .build() ); var purchase = purchaseRepository.save(new Purchase(member)); - ticketRepository.save(new Ticket(null, ticketing)); - ticketRepository.save(new Ticket(purchase, ticketing)); - ticketRepository.save(new Ticket(purchase, ticketing)); + var t1= ticketRepository.save(new Ticket(1000, 10, 7, "title 1", "description 1", ticketing)); + var t2 = ticketRepository.save(new Ticket(2000, 10, 7, "title 2", "description 1", ticketing)); + var t3 = ticketRepository.save(new Ticket(3000, 10, 9, "title 3", "description 1", ticketing)); + + var t1Count = 3; + purchaseItemRepository.saveAll(Arrays.stream(new int[t1Count]) + .mapToObj(i -> new PurchaseItem(purchase, t1)) + .toList()); + var t2Count = 3; + purchaseItemRepository.saveAll(Arrays.stream(new int[t2Count]) + .mapToObj(i -> new PurchaseItem(purchase, t2)) + .toList() + ); + var t3Count = 1; + purchaseItemRepository.saveAll(Arrays.stream(new int[t3Count]) + .mapToObj(i -> new PurchaseItem(purchase, t3)) + .toList() + ); //when var memberTicketingSale = getMemberTicketingSalesUseCase.getMemberTicketingSales( @@ -85,8 +103,8 @@ void getMemberTicketingSalesSuccess() { assertThat(memberTicketingSale.getDescription()).isEqualTo(""); assertThat(memberTicketingSale.getSaleStart()).isEqualTo(now); assertThat(memberTicketingSale.getSaleEnd()).isEqualTo(now); - assertThat(memberTicketingSale.getRemainStock()).isEqualTo(1); - assertThat(memberTicketingSale.getStock()).isEqualTo(3); - + assertThat(memberTicketingSale.getRemainStock()).isEqualTo(23); + assertThat(memberTicketingSale.getStock()).isEqualTo(30); + assertThat(memberTicketingSale.getPrice()).isEqualTo(12000); } } \ No newline at end of file diff --git a/src/test/java/com/tiketeer/Tiketeer/domain/purchase/PurchaseTestHelper.java b/src/test/java/com/tiketeer/Tiketeer/domain/purchase/PurchaseTestHelper.java index dc7cea55..4950654a 100644 --- a/src/test/java/com/tiketeer/Tiketeer/domain/purchase/PurchaseTestHelper.java +++ b/src/test/java/com/tiketeer/Tiketeer/domain/purchase/PurchaseTestHelper.java @@ -3,20 +3,20 @@ import java.util.Collections; import java.util.List; import java.util.UUID; +import java.util.stream.Stream; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.TestComponent; -import org.springframework.data.domain.Limit; import org.springframework.data.util.Pair; import com.tiketeer.Tiketeer.domain.member.exception.MemberNotFoundException; import com.tiketeer.Tiketeer.domain.member.repository.MemberRepository; -import com.tiketeer.Tiketeer.domain.purchase.exception.NotEnoughTicketException; +import com.tiketeer.Tiketeer.domain.purchase.repository.PurchaseItemRepository; import com.tiketeer.Tiketeer.domain.purchase.repository.PurchaseRepository; import com.tiketeer.Tiketeer.domain.ticket.Ticket; +import com.tiketeer.Tiketeer.domain.ticket.exception.NotEnoughRemainingStockException; +import com.tiketeer.Tiketeer.domain.ticket.exception.TicketNotFoundException; import com.tiketeer.Tiketeer.domain.ticket.repository.TicketRepository; -import com.tiketeer.Tiketeer.domain.ticketing.Ticketing; -import com.tiketeer.Tiketeer.domain.ticketing.exception.TicketingNotFoundException; import com.tiketeer.Tiketeer.domain.ticketing.repository.TicketingRepository; @TestComponent @@ -30,29 +30,29 @@ public class PurchaseTestHelper { private TicketRepository ticketRepository; @Autowired private TicketingRepository ticketingRepository; + @Autowired + private PurchaseItemRepository purchaseItemRepository; - public Pair> createPurchase(UUID memberId, UUID ticketingId, int count) { + public Pair> createPurchase(UUID memberId, UUID ticketId, int count) { var member = memberRepository.findById(memberId).orElseThrow(MemberNotFoundException::new); - var ticketing = ticketingRepository.findById(ticketingId).orElseThrow(TicketingNotFoundException::new); + var ticket = ticketRepository.findById(ticketId).orElseThrow(TicketNotFoundException::new); var purchase = purchaseRepository.save(Purchase.builder().member(member).build()); if (count > 0) { - var tickets = updateTicketPurchase(purchase, ticketing, count); - return Pair.of(purchase, tickets); + var purchaseItems = createPurchaseItems(purchase, ticket, count); + return Pair.of(purchase, purchaseItems); } return Pair.of(purchase, Collections.emptyList()); } - private List updateTicketPurchase(Purchase purchase, Ticketing ticketing, int count) { - var tickets = this.ticketRepository.findByTicketingIdAndPurchaseIsNullOrderById(ticketing.getId(), - Limit.of(count)); - if (tickets.size() < count) { - throw new NotEnoughTicketException(); + private List createPurchaseItems(Purchase purchase, Ticket ticket, int count) { + var stock = ticket.getRemainingStock(); + if (stock < count) { + throw new NotEnoughRemainingStockException(); } - tickets.forEach(ticket -> { - ticket.setPurchase(purchase); - this.ticketRepository.save(ticket); - }); - return tickets; + ticket.setRemainingStock(stock - count); + var purchaseItems = Stream.generate(() -> new PurchaseItem(purchase, ticket)).limit(count).toList(); + return purchaseItemRepository.saveAll(purchaseItems); + } } diff --git a/src/test/java/com/tiketeer/Tiketeer/domain/purchase/controller/PurchaseControllerTest.java b/src/test/java/com/tiketeer/Tiketeer/domain/purchase/controller/PurchaseControllerTest.java index 759d0adc..38f45994 100644 --- a/src/test/java/com/tiketeer/Tiketeer/domain/purchase/controller/PurchaseControllerTest.java +++ b/src/test/java/com/tiketeer/Tiketeer/domain/purchase/controller/PurchaseControllerTest.java @@ -6,7 +6,6 @@ import java.nio.file.Path; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.UUID; @@ -29,17 +28,17 @@ import com.tiketeer.Tiketeer.auth.constant.JwtMetadata; import com.tiketeer.Tiketeer.domain.member.Member; import com.tiketeer.Tiketeer.domain.member.repository.MemberRepository; +import com.tiketeer.Tiketeer.domain.purchase.PurchaseItem; import com.tiketeer.Tiketeer.domain.purchase.PurchaseTestHelper; -import com.tiketeer.Tiketeer.domain.purchase.controller.dto.DeletePurchaseTicketsRequestDto; +import com.tiketeer.Tiketeer.domain.purchase.controller.dto.DeletePurchaseItemsRequestDto; import com.tiketeer.Tiketeer.domain.purchase.controller.dto.PostPurchaseRequestDto; +import com.tiketeer.Tiketeer.domain.purchase.repository.PurchaseItemRepository; import com.tiketeer.Tiketeer.domain.purchase.repository.PurchaseRepository; -import com.tiketeer.Tiketeer.domain.purchase.usecase.dto.GetPurchaseTicketsResponseDto; +import com.tiketeer.Tiketeer.domain.purchase.usecase.dto.CreatePurchaseCommandDto; import com.tiketeer.Tiketeer.domain.role.constant.RoleEnum; -import com.tiketeer.Tiketeer.domain.ticket.Ticket; import com.tiketeer.Tiketeer.domain.ticket.repository.TicketRepository; import com.tiketeer.Tiketeer.domain.ticketing.TicketingTestHelper; import com.tiketeer.Tiketeer.infra.redis.RedisService; -import com.tiketeer.Tiketeer.response.ApiResponse; import com.tiketeer.Tiketeer.testhelper.RedisTestHelper; import com.tiketeer.Tiketeer.testhelper.TestHelper; @@ -72,6 +71,8 @@ public class PurchaseControllerTest { private RedisTemplate redisTemplate; @Autowired private RedisTestHelper redisTestHelper; + @Autowired + private PurchaseItemRepository purchaseItemRepository; @TempDir static Path tempDir; @@ -103,9 +104,11 @@ void postPurchaseSuccess() throws Exception { Cookie cookie = new Cookie(JwtMetadata.ACCESS_TOKEN, token); var count = 2; + var tickets = List.of( + new CreatePurchaseCommandDto.Ticket(ticketRepository.findAll().getFirst().getId(), count)); PostPurchaseRequestDto req = PostPurchaseRequestDto.builder() .ticketingId(ticketing.getId()) - .count(count) + .tickets(tickets) .build(); String content = objectMapper.writeValueAsString(req); @@ -140,7 +143,7 @@ void postPurchaseFailPurchaseTokenIsNull() throws Exception { // given var sellerEmail = "seller@test.com"; var seller = testHelper.createMember(sellerEmail, RoleEnum.SELLER); - var ticketing = ticketingTestHelper.createTicketing(seller.getId(), 0, 3000, 2); + var ticketingId = UUID.randomUUID(); var buyerEmail = "buyer@test.com"; String token = testHelper.registerAndLoginAndReturnAccessToken(buyerEmail, RoleEnum.BUYER); @@ -149,9 +152,11 @@ void postPurchaseFailPurchaseTokenIsNull() throws Exception { Cookie cookie = new Cookie(JwtMetadata.ACCESS_TOKEN, token); var count = 2; + var tickets = List.of( + new CreatePurchaseCommandDto.Ticket(UUID.randomUUID(), count)); PostPurchaseRequestDto req = PostPurchaseRequestDto.builder() - .ticketingId(ticketing.getId()) - .count(count) + .ticketingId(ticketingId) + .tickets(tickets) .build(); String content = objectMapper.writeValueAsString(req); @@ -181,9 +186,11 @@ void postPurchaseFailPurchaseTokenNotValid() throws Exception { Cookie cookie = new Cookie(JwtMetadata.ACCESS_TOKEN, token); var count = 2; + var tickets = List.of( + new CreatePurchaseCommandDto.Ticket(UUID.randomUUID(), count)); PostPurchaseRequestDto req = PostPurchaseRequestDto.builder() .ticketingId(ticketingId) - .count(count) + .tickets(tickets) .build(); String content = objectMapper.writeValueAsString(req); @@ -218,9 +225,11 @@ void postPurchaseFailPurchaseTokenOfOtherUser() throws Exception { Cookie cookie = new Cookie(JwtMetadata.ACCESS_TOKEN, token); var count = 2; + var tickets = List.of( + new CreatePurchaseCommandDto.Ticket(UUID.randomUUID(), count)); PostPurchaseRequestDto req = PostPurchaseRequestDto.builder() .ticketingId(ticketingId) - .count(count) + .tickets(tickets) .build(); String content = objectMapper.writeValueAsString(req); @@ -254,9 +263,11 @@ void postPurchaseFailUserNotExistInQueue() throws Exception { Cookie cookie = new Cookie(JwtMetadata.ACCESS_TOKEN, token); var count = 2; + var tickets = List.of( + new CreatePurchaseCommandDto.Ticket(UUID.randomUUID(), count)); PostPurchaseRequestDto req = PostPurchaseRequestDto.builder() .ticketingId(ticketingId) - .count(count) + .tickets(tickets) .build(); String content = objectMapper.writeValueAsString(req); @@ -291,9 +302,11 @@ void postPurchaseFailNotInOrder() throws Exception { Cookie cookie = new Cookie(JwtMetadata.ACCESS_TOKEN, token); var count = 2; + var tickets = List.of( + new CreatePurchaseCommandDto.Ticket(UUID.randomUUID(), count)); PostPurchaseRequestDto req = PostPurchaseRequestDto.builder() .ticketingId(ticketingId) - .count(count) + .tickets(tickets) .build(); String content = objectMapper.writeValueAsString(req); @@ -328,20 +341,20 @@ void deletePurchaseTicketsSuccess() throws Exception { Member member = memberRepository.findAll().getFirst(); Cookie cookie = new Cookie(JwtMetadata.ACCESS_TOKEN, token); var ticketing = ticketingTestHelper.createTicketing(member.getId(), 0, 1000, 2); - var purchaseTicketPair = purchaseTestHelper.createPurchase(member.getId(), ticketing.getId(), 2); + var purchaseTicketPair = purchaseTestHelper.createPurchase(member.getId(), + ticketRepository.findAll().getFirst().getId(), 2); var purchase = purchaseTicketPair.getFirst(); - var tickets = purchaseTicketPair.getSecond(); + var purchaseItems = purchaseTicketPair.getSecond(); - List ticketsToRefund = Collections.singletonList(tickets.getFirst().getId()); - DeletePurchaseTicketsRequestDto req = DeletePurchaseTicketsRequestDto.builder() - .purchaseId(purchase.getId()) - .ticketIds(ticketsToRefund) + List purchaseItemsToRefund = List.of(purchaseItems.getFirst().getId()); + DeletePurchaseItemsRequestDto req = DeletePurchaseItemsRequestDto.builder() + .purchaseItemIds(purchaseItemsToRefund) .build(); String content = objectMapper.writeValueAsString(req); // when mockMvc - .perform(delete("/api/purchases/{purchaseId}/tickets", purchase.getId()) + .perform(delete("/api/purchases/{purchaseId}/items", purchase.getId()) .contextPath("/api") .contentType(MediaType.APPLICATION_JSON) .characterEncoding("utf-8") @@ -350,7 +363,7 @@ void deletePurchaseTicketsSuccess() throws Exception { ) //then .andExpect(status().isOk()); - assertThat(ticketRepository.findAllByPurchase(purchase).size()).isEqualTo(1); + assertThat(purchaseItemRepository.findAll().size()).isEqualTo(1); } @Test @@ -362,20 +375,20 @@ void deletePurchaseAllTicketsSuccess() throws Exception { Member member = memberRepository.findAll().getFirst(); Cookie cookie = new Cookie(JwtMetadata.ACCESS_TOKEN, token); var ticketing = ticketingTestHelper.createTicketing(member.getId(), 0, 1000, 2); - var purchaseTicketPair = purchaseTestHelper.createPurchase(member.getId(), ticketing.getId(), 2); + var ticketId = ticketRepository.findAll().getFirst().getId(); + var purchaseTicketPair = purchaseTestHelper.createPurchase(member.getId(), ticketId, 2); var purchase = purchaseTicketPair.getFirst(); - var tickets = purchaseTicketPair.getSecond(); + var purchaseItems = purchaseTicketPair.getSecond(); - List ticketsToRefund = tickets.stream().map(Ticket::getId).toList(); - DeletePurchaseTicketsRequestDto req = DeletePurchaseTicketsRequestDto.builder() - .purchaseId(purchase.getId()) - .ticketIds(ticketsToRefund) + List purchaseItemsToRefund = purchaseItems.stream().map(PurchaseItem::getId).toList(); + DeletePurchaseItemsRequestDto req = DeletePurchaseItemsRequestDto.builder() + .purchaseItemIds(purchaseItemsToRefund) .build(); String content = objectMapper.writeValueAsString(req); // when mockMvc - .perform(delete("/api/purchases/{purchaseId}/tickets", purchase.getId()) + .perform(delete("/api/purchases/{purchaseId}/items", purchase.getId()) .contextPath("/api") .contentType(MediaType.APPLICATION_JSON) .characterEncoding("utf-8") @@ -385,43 +398,6 @@ void deletePurchaseAllTicketsSuccess() throws Exception { //then .andExpect(status().isOk()); assertThat(purchaseRepository.findById(purchase.getId())).isEmpty(); - assertThat(ticketRepository.findAllByPurchase(purchase).size()).isEqualTo(0); + assertThat(purchaseItemRepository.findAll().size()).isEqualTo(0); } - - @Test - @DisplayName("ํ‹ฐ์ผ“ํŒ… ๋ฐ ํ‹ฐ์ผ“ ์ƒ์„ฑ ๋ฐ ๊ตฌ๋งค > ๊ตฌ๋งค ํ•˜์œ„ ํ‹ฐ์ผ“ ๋ชฉ๋ก ์กฐํšŒ > ์„ฑ๊ณต") - @Transactional - void getPurchaseTicketsSuccess() throws Exception { - //given - String token = testHelper.registerAndLoginAndReturnAccessToken("user@example.com", RoleEnum.SELLER); - Member member = memberRepository.findAll().getFirst(); - Cookie cookie = new Cookie(JwtMetadata.ACCESS_TOKEN, token); - var ticketing = ticketingTestHelper.createTicketing(member.getId(), 1, 1000, 4); - var purchaseTicketPair = purchaseTestHelper.createPurchase(member.getId(), ticketing.getId(), 2); - var purchase = purchaseTicketPair.getFirst(); - var tickets = purchaseTicketPair.getSecond(); - - // when - var result = mockMvc - .perform(get("/api/purchases/{purchaseId}/tickets", purchase.getId()) - .contextPath("/api") - .contentType(MediaType.APPLICATION_JSON) - .characterEncoding("utf-8") - .cookie(cookie) - ) - //then - .andExpect(status().isOk()); - //then - - ApiResponse response = testHelper.getDeserializedApiResponse( - result.andReturn().getResponse().getContentAsString(), - GetPurchaseTicketsResponseDto.class); - - var ticketIds = response.getData().getTicketIds(); - assertThat(ticketIds.size()).isEqualTo(2); - tickets.forEach(ticket -> { - assertThat(ticketIds.contains(ticket.getId())).isTrue(); - }); - } - } diff --git a/src/test/java/com/tiketeer/Tiketeer/domain/purchase/repository/PurchaseItemRepositoryTest.java b/src/test/java/com/tiketeer/Tiketeer/domain/purchase/repository/PurchaseItemRepositoryTest.java new file mode 100644 index 00000000..acc45935 --- /dev/null +++ b/src/test/java/com/tiketeer/Tiketeer/domain/purchase/repository/PurchaseItemRepositoryTest.java @@ -0,0 +1,89 @@ +package com.tiketeer.Tiketeer.domain.purchase.repository; + +import java.time.LocalDateTime; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.transaction.annotation.Transactional; + +import com.tiketeer.Tiketeer.domain.member.Member; +import com.tiketeer.Tiketeer.domain.member.repository.MemberRepository; +import com.tiketeer.Tiketeer.domain.purchase.Purchase; +import com.tiketeer.Tiketeer.domain.purchase.PurchaseItem; +import com.tiketeer.Tiketeer.domain.role.Role; +import com.tiketeer.Tiketeer.domain.role.constant.RoleEnum; +import com.tiketeer.Tiketeer.domain.role.repository.RoleRepository; +import com.tiketeer.Tiketeer.domain.ticket.Ticket; +import com.tiketeer.Tiketeer.domain.ticket.repository.TicketRepository; +import com.tiketeer.Tiketeer.domain.ticketing.Ticketing; +import com.tiketeer.Tiketeer.domain.ticketing.repository.TicketingRepository; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@DataJpaTest +public class PurchaseItemRepositoryTest { + @Autowired + private TicketRepository ticketRepository; + + @Autowired + private PurchaseRepository purchaseRepository; + + @Autowired + private TicketingRepository ticketingRepository; + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private RoleRepository roleRepository; + + @Autowired + private PurchaseItemRepository purchaseItemRepository; + + @Test + @DisplayName("๋ฉค๋ฒ„ ๊ตฌ๋งค ๋‚ด์—ญ ์ƒ์„ฑ > ๋ฉค๋ฒ„ ๊ตฌ๋งค ๋‚ด์—ญ + ํ‹ฐ์ผ“ ์ •๋ณด์™€ ํ•จ๊ป˜ ์กฐํšŒ > ์„ฑ๊ณต") + @Transactional + void findAllByPurchase() throws Throwable { + var mockEmail = "test@test.com"; + var role = roleRepository.save(new Role(RoleEnum.SELLER)); + var member = memberRepository.save( + new Member(mockEmail, "asdf1234", 0L, false, null, role)); + var now = LocalDateTime.now(); + var ticketing1 = ticketingRepository.save( + Ticketing.builder() + .member(member) + .description("") + .title("test1") + .location("Seoul") + .eventTime(now) + .category("") + .runningMinutes(600) + .saleStart(now) + .saleEnd(now) + .build() + ); + + var ticket1 = ticketRepository.save(new Ticket(1000, 3, 3, "ticket1", "", ticketing1)); + + var purchase = purchaseRepository.save(new Purchase(member)); + + var purchaseItem1 = purchaseItemRepository.save(new PurchaseItem(purchase, ticket1)); + var purchaseItem2 = purchaseItemRepository.save(new PurchaseItem(purchase, ticket1)); + + // when + var purchaseItems = purchaseItemRepository.findAllByPurchase(purchase); + + // then + Assertions.assertThat(purchaseItems.size()).isEqualTo(2); + Assertions.assertThat(purchaseItems) + .extracting("id") + .containsExactlyInAnyOrder(purchaseItem1.getId(), purchaseItem2.getId()); + Assertions.assertThat(purchaseItems.getFirst().getTicket().getId()).isEqualTo(ticket1.getId()); + + } + +} diff --git a/src/test/java/com/tiketeer/Tiketeer/domain/purchase/repository/PurchaseRepositoryTest.java b/src/test/java/com/tiketeer/Tiketeer/domain/purchase/repository/PurchaseRepositoryTest.java index 128a2203..ab8f903c 100644 --- a/src/test/java/com/tiketeer/Tiketeer/domain/purchase/repository/PurchaseRepositoryTest.java +++ b/src/test/java/com/tiketeer/Tiketeer/domain/purchase/repository/PurchaseRepositoryTest.java @@ -1,6 +1,7 @@ package com.tiketeer.Tiketeer.domain.purchase.repository; import java.time.LocalDateTime; +import java.util.function.Function; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; @@ -11,7 +12,9 @@ import com.tiketeer.Tiketeer.domain.member.Member; import com.tiketeer.Tiketeer.domain.member.repository.MemberRepository; +import com.tiketeer.Tiketeer.domain.member.usecase.dto.GetMemberPurchase; import com.tiketeer.Tiketeer.domain.purchase.Purchase; +import com.tiketeer.Tiketeer.domain.purchase.PurchaseItem; import com.tiketeer.Tiketeer.domain.role.Role; import com.tiketeer.Tiketeer.domain.role.constant.RoleEnum; import com.tiketeer.Tiketeer.domain.role.repository.RoleRepository; @@ -20,6 +23,9 @@ import com.tiketeer.Tiketeer.domain.ticketing.Ticketing; import com.tiketeer.Tiketeer.domain.ticketing.repository.TicketingRepository; +import lombok.extern.slf4j.Slf4j; + +@Slf4j @DataJpaTest public class PurchaseRepositoryTest { @Autowired @@ -37,10 +43,13 @@ public class PurchaseRepositoryTest { @Autowired private RoleRepository roleRepository; + @Autowired + private PurchaseItemRepository purchaseItemRepository; + @Test @DisplayName("๋ฉค๋ฒ„ ๊ตฌ๋งค ๋‚ด์—ญ ์ƒ์„ฑ > ๋ฉค๋ฒ„ ๊ตฌ๋งค ๋‚ด์—ญ + ํ‹ฐ์ผ“ ์ •๋ณด์™€ ํ•จ๊ป˜ ์กฐํšŒ > ์„ฑ๊ณต") @Transactional - void findWithTicketingByMember() { + void findWithTicketingByMember() throws Throwable { var mockEmail = "test@test.com"; var role = roleRepository.save(new Role(RoleEnum.SELLER)); var member = memberRepository.save( @@ -48,7 +57,6 @@ void findWithTicketingByMember() { var now = LocalDateTime.now(); var ticketing1 = ticketingRepository.save( Ticketing.builder() - .price(1000) .member(member) .description("") .title("test1") @@ -62,7 +70,6 @@ void findWithTicketingByMember() { ); var ticketing2 = ticketingRepository.save( Ticketing.builder() - .price(1000) .member(member) .description("") .title("test2") @@ -74,21 +81,76 @@ void findWithTicketingByMember() { .saleEnd(now) .build() ); + + var ticket1 = ticketRepository.save(new Ticket(1000, 3, 3, "ticket1", "", ticketing1)); + var ticket2 = ticketRepository.save(new Ticket(1000, 1, 1, "ticket2", "", ticketing2)); + var purchase1 = purchaseRepository.save(new Purchase(member)); var purchase2 = purchaseRepository.save(new Purchase(member)); - ticketRepository.save(new Ticket(null, ticketing1)); - ticketRepository.save(new Ticket(purchase1, ticketing1)); - ticketRepository.save(new Ticket(purchase1, ticketing1)); - ticketRepository.save(new Ticket(purchase2, ticketing2)); + + var purchaseItem1 = purchaseItemRepository.save(new PurchaseItem(purchase1, ticket1)); + var purchaseItem2 = purchaseItemRepository.save(new PurchaseItem(purchase1, ticket1)); + var purchaseItem3 = purchaseItemRepository.save(new PurchaseItem(purchase2, ticket2)); // when - var results = purchaseRepository.findWithTicketingByMember(member); + var m = memberRepository.findByEmail(mockEmail).orElseThrow(); + var results = purchaseRepository.findWithTicketingByMember(m); // then Assertions.assertThat(results.size()).isEqualTo(2); - Assertions.assertThat(results.get(0).getCount()).isEqualTo(2); - Assertions.assertThat(results.get(0).getTicketingId()).isEqualTo(ticketing1.getId()); - Assertions.assertThat(results.get(1).getCount()).isEqualTo(1); - Assertions.assertThat(results.get(1).getTicketingId()).isEqualTo(ticketing2.getId()); + Assertions.assertThat(results.get(0).getPurchaseId()).isEqualTo(purchase1.getId()); + Assertions.assertThat(results.get(1).getPurchaseId()).isEqualTo(purchase2.getId()); + + for (int i = 0; i < 2; i++) { + var purchase = results.get(i); + var ticketing = i == 0 ? ticketing1 : ticketing2; + checkProperty(purchase, GetMemberPurchase::getTicketingId, ticketing.getId()); + checkProperty(purchase, GetMemberPurchase::getTitle, ticketing.getTitle()); + checkProperty(purchase, GetMemberPurchase::getLocation, ticketing.getLocation()); + checkProperty(purchase, GetMemberPurchase::getEventTime, ticketing.getEventTime()); + checkProperty(purchase, GetMemberPurchase::getCreatedAt, ticketing.getCreatedAt()); + checkProperty(purchase, GetMemberPurchase::getCategory, ticketing.getCategory()); + checkProperty(purchase, GetMemberPurchase::getRunningMinutes, ticketing.getRunningMinutes()); + } + + Assertions.assertThat(results.get(0).getPurchaseItems()) + .extracting("purchaseItemId") + .containsExactlyInAnyOrder(purchaseItem1.getId(), purchaseItem2.getId()); + Assertions.assertThat(results.get(1).getPurchaseItems()) + .extracting("purchaseItemId") + .containsExactly(purchaseItem3.getId()); + + for (int i = 0; i < 2; i++) { + var purchase = results.get(i); + var ticket = i == 0 ? ticket1 : ticket2; + checkProperty(purchase.getPurchaseItems().getFirst(), + GetMemberPurchase.GetMemberPurchaseItems::getTicketId, ticket.getId()); + checkProperty(purchase.getPurchaseItems().getFirst(), + GetMemberPurchase.GetMemberPurchaseItems::getTitle, ticket.getTitle()); + checkProperty(purchase.getPurchaseItems().getFirst(), + GetMemberPurchase.GetMemberPurchaseItems::getDescription, ticket.getDescription()); + checkProperty(purchase.getPurchaseItems().getFirst(), + GetMemberPurchase.GetMemberPurchaseItems::getPrice, ticket.getPrice()); + if (i == 0) { + checkProperty(purchase.getPurchaseItems().get(1), + GetMemberPurchase.GetMemberPurchaseItems::getTicketId, ticket.getId()); + checkProperty(purchase.getPurchaseItems().get(1), + GetMemberPurchase.GetMemberPurchaseItems::getTitle, ticket.getTitle()); + checkProperty(purchase.getPurchaseItems().get(1), + GetMemberPurchase.GetMemberPurchaseItems::getDescription, ticket.getDescription()); + checkProperty(purchase.getPurchaseItems().get(1), + GetMemberPurchase.GetMemberPurchaseItems::getPrice, ticket.getPrice()); + } + } + } + + private void checkProperty(T object, Function propertyGetter, R expectedValue) { + if (propertyGetter.apply(object) instanceof LocalDateTime time + && expectedValue instanceof LocalDateTime expectedTime) { + Assertions.assertThat(time).isEqualToIgnoringNanos(expectedTime); + } else { + Assertions.assertThat(propertyGetter.apply(object)).isEqualTo(expectedValue); + } + } } diff --git a/src/test/java/com/tiketeer/Tiketeer/domain/purchase/service/PurchaseServiceTest.java b/src/test/java/com/tiketeer/Tiketeer/domain/purchase/service/PurchaseServiceTest.java index e9f9e5c7..c2eec9c4 100644 --- a/src/test/java/com/tiketeer/Tiketeer/domain/purchase/service/PurchaseServiceTest.java +++ b/src/test/java/com/tiketeer/Tiketeer/domain/purchase/service/PurchaseServiceTest.java @@ -1,9 +1,6 @@ package com.tiketeer.Tiketeer.domain.purchase.service; import java.time.LocalDateTime; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.AfterEach; @@ -13,28 +10,27 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Import; -import org.springframework.data.domain.Limit; -import org.springframework.data.util.Pair; import org.springframework.transaction.annotation.Transactional; -import com.tiketeer.Tiketeer.domain.member.Member; -import com.tiketeer.Tiketeer.domain.purchase.Purchase; +import com.tiketeer.Tiketeer.domain.purchase.PurchaseTestHelper; import com.tiketeer.Tiketeer.domain.purchase.exception.AccessForNotOwnedPurchaseException; -import com.tiketeer.Tiketeer.domain.purchase.exception.NotEnoughTicketException; import com.tiketeer.Tiketeer.domain.purchase.exception.PurchaseNotInSalePeriodException; import com.tiketeer.Tiketeer.domain.purchase.repository.PurchaseRepository; -import com.tiketeer.Tiketeer.domain.ticket.Ticket; import com.tiketeer.Tiketeer.domain.ticket.repository.TicketRepository; -import com.tiketeer.Tiketeer.domain.ticketing.Ticketing; +import com.tiketeer.Tiketeer.domain.ticketing.TicketingTestHelper; import com.tiketeer.Tiketeer.domain.ticketing.repository.TicketingRepository; import com.tiketeer.Tiketeer.testhelper.TestHelper; -@Import({TestHelper.class}) +@Import({TestHelper.class, PurchaseTestHelper.class, TicketingTestHelper.class}) @SpringBootTest public class PurchaseServiceTest { @Autowired private TestHelper testHelper; @Autowired + private PurchaseTestHelper purchaseTestHelper; + @Autowired + private TicketingTestHelper ticketingTestHelper; + @Autowired private PurchaseService purchaseService; @Autowired private PurchaseRepository purchaseRepository; @@ -60,7 +56,7 @@ void validateTicketingSalePeriodSuccess() { // given var mockEmail = "test1@test.com"; var member = testHelper.createMember(mockEmail, "1234"); - var ticketing = createTicketing(member, 0, 1); + var ticketing = ticketingTestHelper.createTicketing(member.getId(), 0, 1); // when // then @@ -76,7 +72,7 @@ void validateTicketingSalePeriodThrowErr() { // given var mockEmail = "test1@test.com"; var member = testHelper.createMember(mockEmail, "1234"); - var ticketing = createTicketing(member, 1, 1); + var ticketing = ticketingTestHelper.createTicketing(member.getId(), 1, 1); Assertions.assertThatThrownBy(() -> { // when @@ -87,12 +83,14 @@ void validateTicketingSalePeriodThrowErr() { @Test @DisplayName("๊ตฌ๋งค์ž ๋ณธ์ธ์ด ์ ‘๊ทผ > ๊ตฌ๋งค์ž ์†Œ์œ  ๊ฒ€์ฆ > ํ†ต๊ณผ") + @Transactional void validatePurchaseOwnershipSuccess() { // given var mockEmail = "test1@test.com"; var member = testHelper.createMember(mockEmail, "1234"); - var ticketing = createTicketing(member, 0, 1); - var purchaseTicketPair = createPurchase(member, ticketing, 1); + var ticketing = ticketingTestHelper.createTicketing(member.getId(), 0, 1); + var ticketId = ticketRepository.findAll().getFirst().getId(); + var purchaseTicketPair = purchaseTestHelper.createPurchase(member.getId(), ticketId, 1); var purchase = purchaseTicketPair.getFirst(); // when @@ -104,12 +102,14 @@ void validatePurchaseOwnershipSuccess() { @Test @DisplayName("๊ตฌ๋งค์ž๊ฐ€ ์•„๋‹Œ ์‚ฌ๋žŒ์ด ์ ‘๊ทผ > ๊ตฌ๋งค์ž ์†Œ์œ  ๊ฒ€์ฆ > Throw Err") + @Transactional void validatePurchaseOwnershipThrowErr() { // given var mockEmail = "test1@test.com"; var member = testHelper.createMember(mockEmail, "1234"); - var ticketing = createTicketing(member, 0, 1); - var purchaseTicketPair = createPurchase(member, ticketing, 1); + var ticketing = ticketingTestHelper.createTicketing(member.getId(), 0, 1); + var ticketId = ticketRepository.findAll().getFirst().getId(); + var purchaseTicketPair = purchaseTestHelper.createPurchase(member.getId(), ticketId, 1); var purchase = purchaseTicketPair.getFirst(); Assertions.assertThatThrownBy(() -> { @@ -118,73 +118,4 @@ void validatePurchaseOwnershipThrowErr() { // then }).isInstanceOf(AccessForNotOwnedPurchaseException.class); } - - @Test - @DisplayName("๊ตฌ๋งค ์ƒ์„ฑ > ๊ตฌ๋งค ํ•˜์œ„ ํ‹ฐ์ผ“ ์กฐํšŒ ์š”์ฒญ > ์„ฑ๊ณต") - void findTicketsUnderPurchaseSuccess() { - // given - var mockEmail = "test1@test.com"; - var member = testHelper.createMember(mockEmail, "1234"); - var ticketing = createTicketing(member, 0, 1); - var purchaseTicketPair = createPurchase(member, ticketing, 1); - var purchase = purchaseTicketPair.getFirst(); - var tickets = purchaseTicketPair.getSecond(); - - // when - var ticketsUnderPurchase = purchaseService.findTicketsUnderPurchase(purchase.getId()); - - // then - Assertions.assertThat(tickets.getFirst().getId()).isEqualTo(ticketsUnderPurchase.getFirst().getId()); - Assertions.assertThat(tickets.getFirst().getPurchase().getId()) - .isEqualTo(ticketsUnderPurchase.getFirst().getPurchase().getId()); - Assertions.assertThat(tickets.getFirst().getTicketing().getId()) - .isEqualTo(ticketsUnderPurchase.getFirst().getTicketing().getId()); - Assertions.assertThat(tickets.getFirst().getCreatedAt()) - .isEqualTo(ticketsUnderPurchase.getFirst().getCreatedAt()); - } - - private Ticketing createTicketing(Member member, int saleStartAfterYears, int stock) { - var now = LocalDateTime.now(); - var eventTime = now.plusYears(saleStartAfterYears + 2); - var saleStart = now.plusYears(saleStartAfterYears); - var saleEnd = now.plusYears(saleStartAfterYears + 1); - var ticketing = ticketingRepository.save(Ticketing.builder() - .price(1000) - .title("test") - .member(member) - .description("") - .location("Seoul") - .eventTime(eventTime) - .saleStart(saleStart) - .saleEnd(saleEnd) - .category("concert") - .runningMinutes(300).build()); - ticketRepository.saveAll(Arrays.stream(new int[stock]) - .mapToObj(i -> Ticket.builder().ticketing(ticketing).build()) - .toList()); - return ticketing; - } - - private Pair> createPurchase(Member member, Ticketing ticketing, int count) { - var purchase = this.purchaseRepository.save(Purchase.builder().member(member).build()); - - if (count > 0) { - var tickets = updateTicketPurchase(purchase, ticketing, count); - return Pair.of(purchase, tickets); - } - return Pair.of(purchase, Collections.emptyList()); - } - - private List updateTicketPurchase(Purchase purchase, Ticketing ticketing, int count) { - var tickets = this.ticketRepository.findByTicketingIdAndPurchaseIsNullOrderById(ticketing.getId(), - Limit.of(count)); - if (tickets.size() < count) { - throw new NotEnoughTicketException(); - } - tickets.forEach(ticket -> { - ticket.setPurchase(purchase); - this.ticketRepository.save(ticket); - }); - return tickets; - } } \ No newline at end of file diff --git a/src/test/java/com/tiketeer/Tiketeer/domain/purchase/usecase/CreatePurchaseConcurrencyTest.java b/src/test/java/com/tiketeer/Tiketeer/domain/purchase/usecase/CreatePurchaseConcurrencyTestHelper.java similarity index 84% rename from src/test/java/com/tiketeer/Tiketeer/domain/purchase/usecase/CreatePurchaseConcurrencyTest.java rename to src/test/java/com/tiketeer/Tiketeer/domain/purchase/usecase/CreatePurchaseConcurrencyTestHelper.java index 1c8970aa..7c5a20a1 100644 --- a/src/test/java/com/tiketeer/Tiketeer/domain/purchase/usecase/CreatePurchaseConcurrencyTest.java +++ b/src/test/java/com/tiketeer/Tiketeer/domain/purchase/usecase/CreatePurchaseConcurrencyTestHelper.java @@ -3,6 +3,7 @@ import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -15,13 +16,14 @@ import com.tiketeer.Tiketeer.domain.member.Member; import com.tiketeer.Tiketeer.domain.purchase.usecase.dto.CreatePurchaseCommandDto; import com.tiketeer.Tiketeer.domain.role.constant.RoleEnum; +import com.tiketeer.Tiketeer.domain.ticket.usecase.dto.CreateTicketMetadata; import com.tiketeer.Tiketeer.domain.ticketing.Ticketing; import com.tiketeer.Tiketeer.domain.ticketing.repository.TicketingRepository; import com.tiketeer.Tiketeer.domain.ticketing.service.TicketingStockService; import com.tiketeer.Tiketeer.testhelper.TestHelper; @TestComponent -public class CreatePurchaseConcurrencyTest { +public class CreatePurchaseConcurrencyTestHelper { @Autowired private CreatePurchaseUseCase createPurchaseUseCase; @Autowired @@ -31,7 +33,8 @@ public class CreatePurchaseConcurrencyTest { @Autowired private TestHelper testHelper; - public int makeConcurrency(int threadNums, List buyers, Ticketing ticketing) throws InterruptedException { + public int makeConcurrency(int threadNums, List buyers, UUID ticketingId, UUID ticketId) throws + InterruptedException { ExecutorService executorService = Executors.newFixedThreadPool(threadNums); CountDownLatch startLatch = new CountDownLatch(1); CountDownLatch endLatch = new CountDownLatch(threadNums); @@ -43,10 +46,12 @@ public int makeConcurrency(int threadNums, List buyers, Ticketing ticket executorService.submit(() -> { try { startLatch.await(); + var tickets = List.of( + new CreatePurchaseCommandDto.Ticket(ticketId, 1)); createPurchaseUseCase.createPurchase(CreatePurchaseCommandDto.builder() - .ticketingId(ticketing.getId()) + .ticketingId(ticketingId) .memberEmail(buyers.get(buyerIdx).getEmail()) - .count(1) + .tickets(tickets) .build()); success.getAndIncrement(); } catch (Exception e) { @@ -62,12 +67,11 @@ public int makeConcurrency(int threadNums, List buyers, Ticketing ticket return success.get(); } - public Ticketing createTicketing(Member member, int stock) { + public Ticketing createTicketing(Member member, List tickets) { var now = LocalDateTime.now(); var ticketing = ticketingRepository.save(Ticketing.builder() .member(member) .title("์ œ๋ชฉ") - .price(10000) .location("์„œ์šธ") .category("๋ฐ”์žํšŒ") .runningMinutes(100) @@ -75,7 +79,7 @@ public Ticketing createTicketing(Member member, int stock) { .saleEnd(now.plusMonths(6)) .eventTime(now.plusYears(1)) .build()); - ticketingStockService.createStock(ticketing.getId(), stock); + ticketingStockService.createStock(ticketing.getId(), tickets); return ticketing; } diff --git a/src/test/java/com/tiketeer/Tiketeer/domain/purchase/usecase/CreatePurchaseUseCaseDistributedLockConcurrencyTest.java b/src/test/java/com/tiketeer/Tiketeer/domain/purchase/usecase/CreatePurchaseUseCaseDistributedLockConcurrencyTest.java index 30654872..ef96918e 100644 --- a/src/test/java/com/tiketeer/Tiketeer/domain/purchase/usecase/CreatePurchaseUseCaseDistributedLockConcurrencyTest.java +++ b/src/test/java/com/tiketeer/Tiketeer/domain/purchase/usecase/CreatePurchaseUseCaseDistributedLockConcurrencyTest.java @@ -4,10 +4,7 @@ import java.time.LocalDateTime; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; -import java.util.Set; -import java.util.UUID; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -22,8 +19,10 @@ import com.tiketeer.Tiketeer.domain.member.Member; import com.tiketeer.Tiketeer.domain.member.repository.MemberRepository; +import com.tiketeer.Tiketeer.domain.purchase.repository.PurchaseItemRepository; import com.tiketeer.Tiketeer.domain.purchase.usecase.decorator.CreatePurchaseUseCaseDLock; import com.tiketeer.Tiketeer.domain.role.constant.RoleEnum; +import com.tiketeer.Tiketeer.domain.ticket.Ticket; import com.tiketeer.Tiketeer.domain.ticket.repository.TicketRepository; import com.tiketeer.Tiketeer.domain.ticketing.Ticketing; import com.tiketeer.Tiketeer.domain.ticketing.repository.TicketingRepository; @@ -32,7 +31,7 @@ import com.tiketeer.Tiketeer.testhelper.TestHelper; import com.tiketeer.Tiketeer.testhelper.Transaction; -@Import({TestHelper.class, CreatePurchaseConcurrencyTest.class, Transaction.class, RedisTestHelper.class}) +@Import({TestHelper.class, CreatePurchaseConcurrencyTestHelper.class, Transaction.class, RedisTestHelper.class}) @SpringBootTest class CreatePurchaseUseCaseDistributedLockConcurrencyTest { @Autowired @@ -46,13 +45,15 @@ class CreatePurchaseUseCaseDistributedLockConcurrencyTest { @Autowired private MemberRepository memberRepository; @Autowired - private CreatePurchaseConcurrencyTest createPurchaseConcurrencyTest; + private CreatePurchaseConcurrencyTestHelper createPurchaseConcurrencyTestHelper; @Autowired private Transaction transaction; @Autowired private RedissonClient redissonClient; @Autowired private RedisTestHelper redisTestHelper; + @Autowired + private PurchaseItemRepository purchaseItemRepository; @BeforeEach void initTable() { @@ -76,23 +77,17 @@ void createPurchaseWithConcurrency() throws InterruptedException { int threadNums = 20; var buyers = createBuyers(threadNums); - createPurchaseConcurrencyTest.makeConcurrency(threadNums, buyers, ticketing); + createPurchaseConcurrencyTestHelper.makeConcurrency(threadNums, buyers, ticketing.getId(), + ticketRepository.findAll().getFirst().getId()); //then transaction.invoke(() -> { - var tickets = ticketRepository.findAllByPurchase(null); - assertThat(tickets.size()).isEqualTo(0); + var tickets = ticketRepository.findAll(); + assertThat(tickets.getFirst().getRemainingStock()).isEqualTo(0); //assert all ticket owners are unique - var purchasedTickets = ticketRepository.findAllByPurchaseIsNotNull(); - assertThat(purchasedTickets.size()).isEqualTo(ticketStock); - - var ticketOwnerIdList = purchasedTickets - .stream() - .map(ticket -> ticket.getPurchase().getMember().getId()).toList(); - - Set ticketOwnerIdSet = new HashSet<>(ticketOwnerIdList); - assertThat(ticketOwnerIdSet.size()).isEqualTo(ticketOwnerIdList.size()); + var purchaseItems = purchaseItemRepository.findAll(); + assertThat(purchaseItems.size()).isEqualTo(ticketStock); //assert one purchase per member var allMembers = memberRepository.findAll(); @@ -109,7 +104,6 @@ private Ticketing createTicketing(Member member, int stock) { var ticketing = ticketingRepository.save(Ticketing.builder() .member(member) .title("์ œ๋ชฉ") - .price(10000) .location("์„œ์šธ") .category("๋ฐ”์žํšŒ") .runningMinutes(100) @@ -117,7 +111,7 @@ private Ticketing createTicketing(Member member, int stock) { .saleEnd(now.plusMonths(6)) .eventTime(now.plusYears(1)) .build()); - ticketingStockService.createStock(ticketing.getId(), stock); + ticketRepository.save(new Ticket(1000, stock, stock, "ticket1", "", ticketing)); return ticketing; } diff --git a/src/test/java/com/tiketeer/Tiketeer/domain/purchase/usecase/CreatePurchaseUseCaseOptimisticLockConcurrencyTest.java b/src/test/java/com/tiketeer/Tiketeer/domain/purchase/usecase/CreatePurchaseUseCaseOptimisticLockConcurrencyTest.java index 6acd7940..11a336ae 100644 --- a/src/test/java/com/tiketeer/Tiketeer/domain/purchase/usecase/CreatePurchaseUseCaseOptimisticLockConcurrencyTest.java +++ b/src/test/java/com/tiketeer/Tiketeer/domain/purchase/usecase/CreatePurchaseUseCaseOptimisticLockConcurrencyTest.java @@ -4,10 +4,7 @@ import java.time.LocalDateTime; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; -import java.util.Set; -import java.util.UUID; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -21,8 +18,10 @@ import com.tiketeer.Tiketeer.domain.member.Member; import com.tiketeer.Tiketeer.domain.member.repository.MemberRepository; +import com.tiketeer.Tiketeer.domain.purchase.repository.PurchaseItemRepository; import com.tiketeer.Tiketeer.domain.purchase.usecase.decorator.CreatePurchaseUseCaseOLock; import com.tiketeer.Tiketeer.domain.role.constant.RoleEnum; +import com.tiketeer.Tiketeer.domain.ticket.Ticket; import com.tiketeer.Tiketeer.domain.ticket.repository.TicketRepository; import com.tiketeer.Tiketeer.domain.ticketing.Ticketing; import com.tiketeer.Tiketeer.domain.ticketing.repository.TicketingRepository; @@ -30,7 +29,7 @@ import com.tiketeer.Tiketeer.testhelper.TestHelper; import com.tiketeer.Tiketeer.testhelper.Transaction; -@Import({TestHelper.class, CreatePurchaseConcurrencyTest.class, Transaction.class}) +@Import({TestHelper.class, CreatePurchaseConcurrencyTestHelper.class, Transaction.class}) @SpringBootTest class CreatePurchaseUseCaseOptimisticLockConcurrencyTest { @@ -45,9 +44,11 @@ class CreatePurchaseUseCaseOptimisticLockConcurrencyTest { @Autowired private MemberRepository memberRepository; @Autowired - private CreatePurchaseConcurrencyTest createPurchaseConcurrencyTest; + private CreatePurchaseConcurrencyTestHelper createPurchaseConcurrencyTestHelper; @Autowired private Transaction transaction; + @Autowired + private PurchaseItemRepository purchaseItemRepository; @BeforeEach void initTable() { @@ -70,25 +71,19 @@ void createPurchaseWithConcurrency() throws InterruptedException { int threadNums = 30; var buyers = createBuyers(threadNums); - int success = createPurchaseConcurrencyTest.makeConcurrency(threadNums, buyers, ticketing); + int success = createPurchaseConcurrencyTestHelper.makeConcurrency(threadNums, buyers, ticketing.getId(), + ticketRepository.findAll().getFirst().getId()); //then transaction.invoke(() -> { - var tickets = ticketRepository.findAllByPurchase(null); - assertThat(ticketStock - tickets.size()).isEqualTo(success); + var tickets = ticketRepository.findAll(); + assertThat(tickets.getFirst().getRemainingStock()).isEqualTo(0); var allMembers = memberRepository.findAll(); //assert all ticket owners are unique - var purchasedTickets = ticketRepository.findAllByPurchaseIsNotNull(); - assertThat(purchasedTickets.size()).isEqualTo(success); - - var ticketOwnerIdList = purchasedTickets.stream() - .map(ticket -> ticket.getPurchase().getMember().getId()) - .toList(); - - Set ticketOwnerIdSet = new HashSet<>(ticketOwnerIdList); - assertThat(ticketOwnerIdSet.size()).isEqualTo(ticketOwnerIdList.size()); + var purchaseItems = purchaseItemRepository.findAll(); + assertThat(purchaseItems.size()).isEqualTo(success); //assert one purchase per member var ticketingSuccessMembers = allMembers.stream() @@ -105,7 +100,6 @@ private Ticketing createTicketing(Member member, int stock) { var ticketing = ticketingRepository.save(Ticketing.builder() .member(member) .title("์ œ๋ชฉ") - .price(10000) .location("์„œ์šธ") .category("๋ฐ”์žํšŒ") .runningMinutes(100) @@ -113,7 +107,7 @@ private Ticketing createTicketing(Member member, int stock) { .saleEnd(now.plusMonths(6)) .eventTime(now.plusYears(1)) .build()); - ticketingStockService.createStock(ticketing.getId(), stock); + ticketRepository.save(new Ticket(1000, stock, stock, "ticket1", "", ticketing)); return ticketing; } diff --git a/src/test/java/com/tiketeer/Tiketeer/domain/purchase/usecase/CreatePurchaseUseCasePessimisticLockConcurrencyTest.java b/src/test/java/com/tiketeer/Tiketeer/domain/purchase/usecase/CreatePurchaseUseCasePessimisticLockConcurrencyTest.java index 8803eae5..ba957bd5 100644 --- a/src/test/java/com/tiketeer/Tiketeer/domain/purchase/usecase/CreatePurchaseUseCasePessimisticLockConcurrencyTest.java +++ b/src/test/java/com/tiketeer/Tiketeer/domain/purchase/usecase/CreatePurchaseUseCasePessimisticLockConcurrencyTest.java @@ -4,10 +4,7 @@ import java.time.LocalDateTime; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; -import java.util.Set; -import java.util.UUID; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -21,16 +18,17 @@ import com.tiketeer.Tiketeer.domain.member.Member; import com.tiketeer.Tiketeer.domain.member.repository.MemberRepository; +import com.tiketeer.Tiketeer.domain.purchase.repository.PurchaseItemRepository; import com.tiketeer.Tiketeer.domain.purchase.usecase.decorator.CreatePurchaseUseCasePLock; import com.tiketeer.Tiketeer.domain.role.constant.RoleEnum; +import com.tiketeer.Tiketeer.domain.ticket.Ticket; import com.tiketeer.Tiketeer.domain.ticket.repository.TicketRepository; import com.tiketeer.Tiketeer.domain.ticketing.Ticketing; import com.tiketeer.Tiketeer.domain.ticketing.repository.TicketingRepository; -import com.tiketeer.Tiketeer.domain.ticketing.service.TicketingStockService; import com.tiketeer.Tiketeer.testhelper.TestHelper; import com.tiketeer.Tiketeer.testhelper.Transaction; -@Import({TestHelper.class, CreatePurchaseConcurrencyTest.class, Transaction.class}) +@Import({TestHelper.class, CreatePurchaseConcurrencyTestHelper.class, Transaction.class}) @SpringBootTest class CreatePurchaseUseCasePessimisticLockConcurrencyTest { @@ -41,13 +39,13 @@ class CreatePurchaseUseCasePessimisticLockConcurrencyTest { @Autowired private TicketingRepository ticketingRepository; @Autowired - private TicketingStockService ticketingStockService; - @Autowired private MemberRepository memberRepository; @Autowired - private CreatePurchaseConcurrencyTest createPurchaseConcurrencyTest; + private CreatePurchaseConcurrencyTestHelper createPurchaseConcurrencyTestHelper; @Autowired private Transaction transaction; + @Autowired + private PurchaseItemRepository purchaseItemRepository; @BeforeEach void initTable() { @@ -70,27 +68,20 @@ void createPurchaseWithConcurrency() throws InterruptedException { int threadNums = 20; var buyers = createBuyers(threadNums); - createPurchaseConcurrencyTest.makeConcurrency(threadNums, buyers, ticketing); + createPurchaseConcurrencyTestHelper.makeConcurrency(threadNums, buyers, ticketing.getId(), + ticketRepository.findAll().getFirst().getId()); //then transaction.invoke(() -> { - var tickets = ticketRepository.findAllByPurchase(null); - assertThat(tickets.size()).isEqualTo(0); - - var allMembers = memberRepository.findAll(); + var tickets = ticketRepository.findAll(); + assertThat(tickets.getFirst().getRemainingStock()).isEqualTo(0); //assert all ticket owners are unique - var purchasedTickets = ticketRepository.findAllByPurchaseIsNotNull(); - assertThat(purchasedTickets.size()).isEqualTo(ticketStock); - - var ticketOwnerIdList = purchasedTickets.stream() - .map(ticket -> ticket.getPurchase().getMember().getId()) - .toList(); - - Set ticketOwnerIdSet = new HashSet<>(ticketOwnerIdList); - assertThat(ticketOwnerIdSet.size()).isEqualTo(ticketOwnerIdList.size()); + var purchaseItems = purchaseItemRepository.findAll(); + assertThat(purchaseItems.size()).isEqualTo(ticketStock); //assert one purchase per member + var allMembers = memberRepository.findAll(); var ticketingSuccessMembers = allMembers.stream() .filter(member -> member.getPurchases().size() == 1) .toList(); @@ -105,7 +96,6 @@ private Ticketing createTicketing(Member member, int stock) { var ticketing = ticketingRepository.save(Ticketing.builder() .member(member) .title("์ œ๋ชฉ") - .price(10000) .location("์„œ์šธ") .category("๋ฐ”์žํšŒ") .runningMinutes(100) @@ -113,7 +103,7 @@ private Ticketing createTicketing(Member member, int stock) { .saleEnd(now.plusMonths(6)) .eventTime(now.plusYears(1)) .build()); - ticketingStockService.createStock(ticketing.getId(), stock); + ticketRepository.save(new Ticket(1000, stock, stock, "ticket1", "", ticketing)); return ticketing; } diff --git a/src/test/java/com/tiketeer/Tiketeer/domain/purchase/usecase/CreatePurchaseUseCaseTest.java b/src/test/java/com/tiketeer/Tiketeer/domain/purchase/usecase/CreatePurchaseUseCaseTest.java index 0af6c5f4..e9c4b2a3 100644 --- a/src/test/java/com/tiketeer/Tiketeer/domain/purchase/usecase/CreatePurchaseUseCaseTest.java +++ b/src/test/java/com/tiketeer/Tiketeer/domain/purchase/usecase/CreatePurchaseUseCaseTest.java @@ -1,7 +1,6 @@ package com.tiketeer.Tiketeer.domain.purchase.usecase; -import java.time.LocalDateTime; -import java.util.Arrays; +import java.util.List; import java.util.UUID; import org.assertj.core.api.Assertions; @@ -14,30 +13,38 @@ import org.springframework.context.annotation.Import; import org.springframework.transaction.annotation.Transactional; -import com.tiketeer.Tiketeer.domain.member.Member; import com.tiketeer.Tiketeer.domain.member.exception.MemberNotFoundException; import com.tiketeer.Tiketeer.domain.member.exception.NotEnoughPointException; -import com.tiketeer.Tiketeer.domain.purchase.exception.NotEnoughTicketException; import com.tiketeer.Tiketeer.domain.purchase.exception.PurchaseNotInSalePeriodException; +import com.tiketeer.Tiketeer.domain.purchase.repository.PurchaseItemRepository; +import com.tiketeer.Tiketeer.domain.purchase.repository.PurchaseRepository; import com.tiketeer.Tiketeer.domain.purchase.usecase.dto.CreatePurchaseCommandDto; import com.tiketeer.Tiketeer.domain.role.constant.RoleEnum; import com.tiketeer.Tiketeer.domain.ticket.Ticket; +import com.tiketeer.Tiketeer.domain.ticket.exception.NotEnoughRemainingStockException; +import com.tiketeer.Tiketeer.domain.ticket.exception.TicketNotFoundException; import com.tiketeer.Tiketeer.domain.ticket.repository.TicketRepository; -import com.tiketeer.Tiketeer.domain.ticketing.Ticketing; +import com.tiketeer.Tiketeer.domain.ticketing.TicketingTestHelper; import com.tiketeer.Tiketeer.domain.ticketing.repository.TicketingRepository; import com.tiketeer.Tiketeer.testhelper.TestHelper; -@Import({TestHelper.class}) +@Import({TestHelper.class, TicketingTestHelper.class}) @SpringBootTest public class CreatePurchaseUseCaseTest { @Autowired private TestHelper testHelper; @Autowired + private TicketingTestHelper ticketingTestHelper; + @Autowired private CreatePurchaseUseCase createPurchaseUseCase; @Autowired private TicketRepository ticketRepository; @Autowired private TicketingRepository ticketingRepository; + @Autowired + private PurchaseRepository purchaseRepository; + @Autowired + private PurchaseItemRepository purchaseItemRepository; @BeforeEach void initTable() { @@ -59,24 +66,34 @@ void createPurchaseSuccess() { var member = testHelper.createMember(mockEmail, "1234"); member.setPoint(initPoint); - var ticketing = createTicketing(member, 0, 3000, 5); + var price = 1000; + var stock = 5; + var ticketing = ticketingTestHelper.createTicketing(member.getId(), 0); + var ticket1 = ticketRepository.save(new Ticket(price, stock, stock, "ticket1", "", ticketing)); + var ticket2 = ticketRepository.save(new Ticket(price, stock, stock, "ticket2", "", ticketing)); var purchaseCount = 3; + var tickets = List.of(new CreatePurchaseCommandDto.Ticket(ticket1.getId(), purchaseCount), + new CreatePurchaseCommandDto.Ticket(ticket2.getId(), purchaseCount)); var createPurchaseCommand = CreatePurchaseCommandDto.builder() .memberEmail(mockEmail) .ticketingId(ticketing.getId()) - .count(purchaseCount) + .tickets(tickets) .build(); // when var result = createPurchaseUseCase.createPurchase(createPurchaseCommand); // then - var purchaseUnderMember = member.getPurchases().getFirst(); - Assertions.assertThat(purchaseUnderMember.getId()).isEqualTo(result.getPurchaseId()); - - var tickets = ticketRepository.findAllByPurchase(purchaseUnderMember); - Assertions.assertThat(tickets.size()).isEqualTo(purchaseCount); + var purchaseUnderMember = purchaseRepository.findWithTicketingByMember(member); + Assertions.assertThat(purchaseUnderMember.getFirst().getPurchaseId()).isEqualTo(result.getPurchaseId()); + Assertions.assertThat(purchaseUnderMember.getFirst().getPurchaseItems().size()).isEqualTo(purchaseCount * 2); + Assertions.assertThat(member.getPoint()).isEqualTo(initPoint - price * purchaseCount * 2); + + var ticketsInDb = ticketRepository.findAll(); + for (var ticket : ticketsInDb) { + Assertions.assertThat(ticket.getRemainingStock()).isEqualTo(stock - purchaseCount); + } } @Test @@ -86,10 +103,12 @@ void createPurchaseFailMemberNotFound() { // given var mockEmail = "test1@test.com"; + var tickets = List.of( + new CreatePurchaseCommandDto.Ticket(UUID.randomUUID(), 1)); var createPurchaseCommand = CreatePurchaseCommandDto.builder() .memberEmail(mockEmail) .ticketingId(UUID.randomUUID()) - .count(1) + .tickets(tickets) .build(); Assertions.assertThatThrownBy(() -> { @@ -106,13 +125,15 @@ void createPurchaseFailNotInSalePeriod() { // given var mockEmail = "test1@test.com"; var member = testHelper.createMember(mockEmail, "1234"); - var ticketing = createTicketing(member, 1, 1000, 1); - var ticketingInDb = ticketingRepository.findById(ticketing.getId()); + var ticketing = ticketingTestHelper.createTicketing(member.getId(), 1, 1000, 1); + var count = 1; + var tickets = List.of( + new CreatePurchaseCommandDto.Ticket(ticketRepository.findAll().getFirst().getId(), count)); var createPurchaseCommand = CreatePurchaseCommandDto.builder() .memberEmail(mockEmail) .ticketingId(ticketing.getId()) - .count(1) + .tickets(tickets) .build(); Assertions.assertThatThrownBy(() -> { @@ -129,40 +150,48 @@ void createPurchaseFailNotEnoughTicket() { // given var sellerEmail = "test1@test.com"; var seller = testHelper.createMember(sellerEmail, RoleEnum.SELLER); - var ticketing = createTicketing(seller, 0, 1000, 3); + var ticketing = ticketingTestHelper.createTicketing(seller.getId(), 0, 1000, 3); var buyerEmail = "test2@test.com"; var buyer = testHelper.createMember(buyerEmail, RoleEnum.BUYER); buyer.setPoint(10000); + + var count = 5; + var tickets = List.of( + new CreatePurchaseCommandDto.Ticket(ticketRepository.findAll().getFirst().getId(), count)); var createPurchaseCommand = CreatePurchaseCommandDto.builder() .memberEmail(buyerEmail) .ticketingId(ticketing.getId()) - .count(5) + .tickets(tickets) .build(); Assertions.assertThatThrownBy(() -> { // when createPurchaseUseCase.createPurchase(createPurchaseCommand); // then - }).isInstanceOf(NotEnoughTicketException.class); + }).isInstanceOf(NotEnoughRemainingStockException.class); } @Test @DisplayName("๊ตฌ๋งคํ•˜๊ธฐ ์œ„ํ•œ ๊ธˆ์•ก์ด ๋ถ€์กฑ > ๊ตฌ๋งค ์š”์ฒญ > ์‹คํŒจ") + @Transactional void createPurchaseFailBecauseNotEnoughPoint() { // given var sellerEmail = "test@test.com"; var seller = testHelper.createMember(sellerEmail, RoleEnum.SELLER); - var ticketing = createTicketing(seller, 0, 3000, 5); + var ticketing = ticketingTestHelper.createTicketing(seller.getId(), 0, 3000, 5); var buyerEmail = "test2@test.com"; var buyer = testHelper.createMember(buyerEmail, RoleEnum.BUYER); buyer.setPoint(5000); + var count = 3; + var tickets = List.of( + new CreatePurchaseCommandDto.Ticket(ticketRepository.findAll().getFirst().getId(), count)); var createPurchaseCommand = CreatePurchaseCommandDto.builder() .memberEmail(buyerEmail) .ticketingId(ticketing.getId()) - .count(3) + .tickets(tickets) .build(); Assertions.assertThatThrownBy(() -> { @@ -172,25 +201,32 @@ void createPurchaseFailBecauseNotEnoughPoint() { }).isInstanceOf(NotEnoughPointException.class); } - private Ticketing createTicketing(Member member, int saleStartAfterYears, long price, int stock) { - var now = LocalDateTime.now(); - var eventTime = now.plusYears(saleStartAfterYears + 2); - var saleStart = now.plusYears(saleStartAfterYears); - var saleEnd = now.plusYears(saleStartAfterYears + 1); - var ticketing = ticketingRepository.save(Ticketing.builder() - .price(price) - .title("test") - .member(member) - .description("") - .location("Seoul") - .eventTime(eventTime) - .saleStart(saleStart) - .saleEnd(saleEnd) - .category("concert") - .runningMinutes(300).build()); - ticketRepository.saveAll(Arrays.stream(new int[stock]) - .mapToObj(i -> Ticket.builder().ticketing(ticketing).build()) - .toList()); - return ticketing; + @Test + @DisplayName("๊ตฌ๋งคํ•˜๊ณ ์ž ํ•˜๋Š” ํ‹ฐ์ผ“์ด ์กด์žฌํ•˜์ง€ ์•Š์Œ > ๊ตฌ๋งค ์š”์ฒญ > ์‹คํŒจ") + @Transactional + void createPurchaseFailBecauseTicketNotFound() { + // given + var sellerEmail = "test@test.com"; + var seller = testHelper.createMember(sellerEmail, RoleEnum.SELLER); + var ticketing = ticketingTestHelper.createTicketing(seller.getId(), 0, 3000, 5); + + var buyerEmail = "test2@test.com"; + var buyer = testHelper.createMember(buyerEmail, RoleEnum.BUYER); + buyer.setPoint(5000); + + var count = 3; + var tickets = List.of( + new CreatePurchaseCommandDto.Ticket(UUID.randomUUID(), count)); + var createPurchaseCommand = CreatePurchaseCommandDto.builder() + .memberEmail(buyerEmail) + .ticketingId(ticketing.getId()) + .tickets(tickets) + .build(); + + Assertions.assertThatThrownBy(() -> { + // when + createPurchaseUseCase.createPurchase(createPurchaseCommand); + // then + }).isInstanceOf(TicketNotFoundException.class); } } diff --git a/src/test/java/com/tiketeer/Tiketeer/domain/purchase/usecase/DeletePurchaseItemsUseCaseTest.java b/src/test/java/com/tiketeer/Tiketeer/domain/purchase/usecase/DeletePurchaseItemsUseCaseTest.java new file mode 100644 index 00000000..6b55fcd4 --- /dev/null +++ b/src/test/java/com/tiketeer/Tiketeer/domain/purchase/usecase/DeletePurchaseItemsUseCaseTest.java @@ -0,0 +1,208 @@ +package com.tiketeer.Tiketeer.domain.purchase.usecase; + +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +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; +import org.springframework.context.annotation.Import; +import org.springframework.transaction.annotation.Transactional; + +import com.tiketeer.Tiketeer.domain.member.repository.MemberRepository; +import com.tiketeer.Tiketeer.domain.purchase.PurchaseItem; +import com.tiketeer.Tiketeer.domain.purchase.PurchaseTestHelper; +import com.tiketeer.Tiketeer.domain.purchase.exception.AccessForNotOwnedPurchaseException; +import com.tiketeer.Tiketeer.domain.purchase.exception.PurchaseNotFoundException; +import com.tiketeer.Tiketeer.domain.purchase.exception.PurchaseNotInSalePeriodException; +import com.tiketeer.Tiketeer.domain.purchase.repository.PurchaseItemRepository; +import com.tiketeer.Tiketeer.domain.purchase.repository.PurchaseRepository; +import com.tiketeer.Tiketeer.domain.purchase.usecase.dto.DeletePurchaseItemsCommandDto; +import com.tiketeer.Tiketeer.domain.ticket.repository.TicketRepository; +import com.tiketeer.Tiketeer.domain.ticketing.TicketingTestHelper; +import com.tiketeer.Tiketeer.domain.ticketing.repository.TicketingRepository; +import com.tiketeer.Tiketeer.testhelper.TestHelper; + +@Import({TestHelper.class, TicketingTestHelper.class, PurchaseTestHelper.class}) +@SpringBootTest +public class DeletePurchaseItemsUseCaseTest { + @Autowired + private TestHelper testHelper; + @Autowired + private TicketingTestHelper ticketingTestHelper; + @Autowired + private PurchaseTestHelper purchaseTestHelper; + @Autowired + private DeletePurchaseItemsUseCase deletePurchaseItemsUseCase; + @Autowired + private PurchaseRepository purchaseRepository; + @Autowired + private TicketRepository ticketRepository; + @Autowired + private TicketingRepository ticketingRepository; + @Autowired + private MemberRepository memberRepository; + @Autowired + private PurchaseItemRepository purchaseItemRepository; + + @BeforeEach + void initTable() { + testHelper.initDB(); + } + + @AfterEach + void cleanTable() { + testHelper.cleanDB(); + } + + @Test + @DisplayName("๊ตฌ๋งค ๋‚ด์—ญ ์ผ๋ถ€ ํ™˜๋ถˆ > ํ‹ฐ์ผ“ ํ™˜๋ถˆ ์š”์ฒญ > ์„ฑ๊ณต") + @Transactional + void deletePurchaseTicketsSuccess() { + // given + var mockEmail = "test1@test.com"; + var member = testHelper.createMember(mockEmail, "1q2w3e4r!!"); + var price = 1000; + var ticketing = ticketingTestHelper.createTicketing(member.getId(), 0, price, 5); + var ticketId = ticketRepository.findAll().getFirst().getId(); + var purchaseTicketPair = purchaseTestHelper.createPurchase(member.getId(), ticketId, 2); + var purchase = purchaseTicketPair.getFirst(); + var purchaseItems = purchaseTicketPair.getSecond(); + + List purchaseItemsToRefund = List.of(purchaseItems.getFirst().getId()); + var deletePurchaseCommand = DeletePurchaseItemsCommandDto.builder() + .memberEmail(mockEmail) + .purchaseId(purchase.getId()) + .purchaseItemIds(purchaseItemsToRefund) + .build(); + + // when + deletePurchaseItemsUseCase.deletePurchaseItems(deletePurchaseCommand); + + // then + var purchaseInDbOpt = purchaseRepository.findById(purchase.getId()); + Assertions.assertThat(purchaseInDbOpt.isPresent()).isTrue(); + + var purchaseItemsInDb = purchaseItemRepository.findAllByPurchase(purchase); + Assertions.assertThat(purchaseItemsInDb.size()).isEqualTo(1); + + var memberInDB = memberRepository.findByEmail(mockEmail).orElseThrow(); + Assertions.assertThat(memberInDB.getPoint()).isEqualTo(price); + } + + @Test + @DisplayName("๊ตฌ๋งค ๋‚ด์—ญ ์ „์ฒด ํ™˜๋ถˆ > ํ‹ฐ์ผ“ ํ™˜๋ถˆ ์š”์ฒญ > ์„ฑ๊ณต") + @Transactional + void deletePurchaseAllTicketsSuccess() { + // given + var mockEmail = "test1@test.com"; + var member = testHelper.createMember(mockEmail, "1234"); + var price = 1000; + var ticketing = ticketingTestHelper.createTicketing(member.getId(), 0, price, 5); + var ticketId = ticketRepository.findAll().getFirst().getId(); + var purchaseTicketPair = purchaseTestHelper.createPurchase(member.getId(), ticketId, 2); + var purchase = purchaseTicketPair.getFirst(); + var purchaseItems = purchaseTicketPair.getSecond(); + + List purchaseItemsToRefund = purchaseItems.stream().map(PurchaseItem::getId).toList(); + var deletePurchaseCommand = DeletePurchaseItemsCommandDto.builder() + .memberEmail(mockEmail) + .purchaseId(purchase.getId()) + .purchaseItemIds(purchaseItemsToRefund) + .build(); + + // when + deletePurchaseItemsUseCase.deletePurchaseItems(deletePurchaseCommand); + + // then + var purchaseInDbOpt = purchaseRepository.findById(purchase.getId()); + Assertions.assertThat(purchaseInDbOpt.isPresent()).isFalse(); + + var purchaseItemsInDb = purchaseItemRepository.findAllByPurchase(purchase); + Assertions.assertThat(purchaseItemsInDb.size()).isEqualTo(0); + + var memberInDB = memberRepository.findByEmail(mockEmail).orElseThrow(); + Assertions.assertThat(memberInDB.getPoint()).isEqualTo(price * purchaseItemsToRefund.size()); + } + + @Test + @DisplayName("๊ตฌ๋งค ๋‚ด์—ญ์ด ์กด์žฌํ•˜์ง€ ์•Š์Œ > ํ‹ฐ์ผ“ ํ™˜๋ถˆ ์š”์ฒญ > ์‹คํŒจ") + @Transactional + void deletePurchaseTicketsFailPurchaseNotFound() { + // given + var mockEmail = "test1@test.com"; + testHelper.createMember(mockEmail, "1234"); + + List purchaseItemsToRefund = List.of(UUID.randomUUID()); + var deletePurchaseCommand = DeletePurchaseItemsCommandDto.builder() + .memberEmail(mockEmail) + .purchaseId(UUID.randomUUID()) + .purchaseItemIds(purchaseItemsToRefund) + .build(); + + Assertions.assertThatThrownBy(() -> { + // when + deletePurchaseItemsUseCase.deletePurchaseItems(deletePurchaseCommand); + // then + }).isInstanceOf(PurchaseNotFoundException.class); + } + + @Test + @DisplayName("๊ตฌ๋งค ๋‚ด์—ญ ์†Œ์œ ์ž๊ฐ€ ์•„๋‹˜ > ํ‹ฐ์ผ“ ํ™˜๋ถˆ ์š”์ฒญ > ์‹คํŒจ") + @Transactional + void deletePurchaseTicketsFailNotPurchaseOwner() { + // given + var mockEmail = "test1@test.com"; + var member = testHelper.createMember(mockEmail, "1234"); + var ticketing = ticketingTestHelper.createTicketing(member.getId(), 0, 5); + var ticketId = ticketRepository.findAll().getFirst().getId(); + var purchaseTicketPair = purchaseTestHelper.createPurchase(member.getId(), ticketId, 1); + var purchase = purchaseTicketPair.getFirst(); + var purchaseItems = purchaseTicketPair.getSecond(); + + List purchaseItemsToRefund = List.of(purchaseItems.getFirst().getId()); + var deletePurchaseCommand = DeletePurchaseItemsCommandDto.builder() + .memberEmail("another@test.com") + .purchaseId(purchase.getId()) + .purchaseItemIds(purchaseItemsToRefund) + .build(); + + Assertions.assertThatThrownBy(() -> { + // when + deletePurchaseItemsUseCase.deletePurchaseItems(deletePurchaseCommand); + // then + }).isInstanceOf(AccessForNotOwnedPurchaseException.class); + } + + @Test + @DisplayName("ํ‹ฐ์ผ“ํŒ… ํŒ๋งค ๊ธฐ๊ฐ„์ด ์•„๋‹˜ > ํ‹ฐ์ผ“ ํ™˜๋ถˆ ์š”์ฒญ > ์‹คํŒจ") + @Transactional + void deletePurchaseTicketsFailNotTicketingSalePeriod() { + // given + var mockEmail = "test1@test.com"; + var member = testHelper.createMember(mockEmail, "1234"); + var ticketing = ticketingTestHelper.createTicketing(member.getId(), -2, 1); + var ticketId = ticketRepository.findAll().getFirst().getId(); + var purchaseTicketPair = purchaseTestHelper.createPurchase(member.getId(), ticketId, 1); + var purchase = purchaseTicketPair.getFirst(); + var purchaseItems = purchaseTicketPair.getSecond(); + + List purchaseItemsToRefund = Collections.singletonList(purchaseItems.getFirst().getId()); + var deletePurchaseCommand = DeletePurchaseItemsCommandDto.builder() + .memberEmail(mockEmail) + .purchaseId(purchase.getId()) + .purchaseItemIds(purchaseItemsToRefund) + .build(); + + Assertions.assertThatThrownBy(() -> { + // when + deletePurchaseItemsUseCase.deletePurchaseItems(deletePurchaseCommand); + // then + }).isInstanceOf(PurchaseNotInSalePeriodException.class); + } +} diff --git a/src/test/java/com/tiketeer/Tiketeer/domain/purchase/usecase/DeletePurchaseTicketsUseCaseTest.java b/src/test/java/com/tiketeer/Tiketeer/domain/purchase/usecase/DeletePurchaseTicketsUseCaseTest.java deleted file mode 100644 index 3d26af7f..00000000 --- a/src/test/java/com/tiketeer/Tiketeer/domain/purchase/usecase/DeletePurchaseTicketsUseCaseTest.java +++ /dev/null @@ -1,269 +0,0 @@ -package com.tiketeer.Tiketeer.domain.purchase.usecase; - -import java.time.LocalDateTime; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.UUID; - -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -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; -import org.springframework.context.annotation.Import; -import org.springframework.data.domain.Limit; -import org.springframework.data.util.Pair; -import org.springframework.transaction.annotation.Transactional; - -import com.tiketeer.Tiketeer.domain.member.Member; -import com.tiketeer.Tiketeer.domain.member.repository.MemberRepository; -import com.tiketeer.Tiketeer.domain.purchase.Purchase; -import com.tiketeer.Tiketeer.domain.purchase.exception.AccessForNotOwnedPurchaseException; -import com.tiketeer.Tiketeer.domain.purchase.exception.EmptyPurchaseException; -import com.tiketeer.Tiketeer.domain.purchase.exception.NotEnoughTicketException; -import com.tiketeer.Tiketeer.domain.purchase.exception.PurchaseNotFoundException; -import com.tiketeer.Tiketeer.domain.purchase.exception.PurchaseNotInSalePeriodException; -import com.tiketeer.Tiketeer.domain.purchase.repository.PurchaseRepository; -import com.tiketeer.Tiketeer.domain.purchase.usecase.dto.DeletePurchaseTicketsCommandDto; -import com.tiketeer.Tiketeer.domain.ticket.Ticket; -import com.tiketeer.Tiketeer.domain.ticket.repository.TicketRepository; -import com.tiketeer.Tiketeer.domain.ticketing.Ticketing; -import com.tiketeer.Tiketeer.domain.ticketing.repository.TicketingRepository; -import com.tiketeer.Tiketeer.testhelper.TestHelper; - -@Import({TestHelper.class}) -@SpringBootTest -public class DeletePurchaseTicketsUseCaseTest { - @Autowired - private TestHelper testHelper; - @Autowired - private DeletePurchaseTicketsUseCase deletePurchaseTicketsUseCase; - @Autowired - private PurchaseRepository purchaseRepository; - @Autowired - private TicketRepository ticketRepository; - @Autowired - private TicketingRepository ticketingRepository; - @Autowired - private MemberRepository memberRepository; - - @BeforeEach - void initTable() { - testHelper.initDB(); - } - - @AfterEach - void cleanTable() { - testHelper.cleanDB(); - } - - @Test - @DisplayName("๊ตฌ๋งค ๋‚ด์—ญ ์ผ๋ถ€ ํ™˜๋ถˆ > ํ‹ฐ์ผ“ ํ™˜๋ถˆ ์š”์ฒญ > ์„ฑ๊ณต") - @Transactional - void deletePurchaseTicketsSuccess() { - // given - var mockEmail = "test1@test.com"; - var member = testHelper.createMember(mockEmail, "1q2w3e4r!!"); - var ticketing = createTicketing(member, 0, 5); - var purchaseTicketPair = createPurchase(member, ticketing, 2); - var purchase = purchaseTicketPair.getFirst(); - var tickets = purchaseTicketPair.getSecond(); - - List ticketsToRefund = Collections.singletonList(tickets.getFirst().getId()); - var deletePurchaseCommand = DeletePurchaseTicketsCommandDto.builder() - .memberEmail(mockEmail) - .purchaseId(purchase.getId()) - .ticketIds(ticketsToRefund) - .build(); - - // when - deletePurchaseTicketsUseCase.deletePurchaseTickets(deletePurchaseCommand); - - // then - var purchaseInDbOpt = purchaseRepository.findById(purchase.getId()); - Assertions.assertThat(purchaseInDbOpt.isPresent()).isTrue(); - - var ticketsUnderPurchase = ticketRepository.findAllByPurchase(purchase); - Assertions.assertThat(ticketsUnderPurchase.size()).isEqualTo(1); - } - - @Test - @DisplayName("๊ตฌ๋งค ๋‚ด์—ญ ์ „์ฒด ํ™˜๋ถˆ > ํ‹ฐ์ผ“ ํ™˜๋ถˆ ์š”์ฒญ > ์„ฑ๊ณต") - @Transactional - void deletePurchaseAllTicketsSuccess() { - // given - var mockEmail = "test1@test.com"; - var member = testHelper.createMember(mockEmail, "1234"); - var ticketing = createTicketing(member, 0, 5); - var purchaseTicketPair = createPurchase(member, ticketing, 2); - var purchase = purchaseTicketPair.getFirst(); - var tickets = purchaseTicketPair.getSecond(); - - List ticketsToRefund = tickets.stream().map(Ticket::getId).toList(); - var deletePurchaseCommand = DeletePurchaseTicketsCommandDto.builder() - .memberEmail(mockEmail) - .purchaseId(purchase.getId()) - .ticketIds(ticketsToRefund) - .build(); - - // when - deletePurchaseTicketsUseCase.deletePurchaseTickets(deletePurchaseCommand); - - // then - var purchaseInDbOpt = purchaseRepository.findById(purchase.getId()); - Assertions.assertThat(purchaseInDbOpt.isPresent()).isFalse(); - - var ticketsUnderPurchase = ticketRepository.findAllByPurchase(purchase); - Assertions.assertThat(ticketsUnderPurchase.size()).isEqualTo(0); - - var memberInDB = memberRepository.findByEmail(mockEmail).orElseThrow(); - Assertions.assertThat(member.getPoint()).isEqualTo(1000L * ticketsToRefund.size()); - } - - @Test - @DisplayName("๊ตฌ๋งค ๋‚ด์—ญ์ด ์กด์žฌํ•˜์ง€ ์•Š์Œ > ํ‹ฐ์ผ“ ํ™˜๋ถˆ ์š”์ฒญ > ์‹คํŒจ") - @Transactional - void deletePurchaseTicketsFailPurchaseNotFound() { - // given - var mockEmail = "test1@test.com"; - testHelper.createMember(mockEmail, "1234"); - - List ticketsToRefund = Collections.singletonList(UUID.randomUUID()); - var deletePurchaseCommand = DeletePurchaseTicketsCommandDto.builder() - .memberEmail(mockEmail) - .purchaseId(UUID.randomUUID()) - .ticketIds(ticketsToRefund) - .build(); - - Assertions.assertThatThrownBy(() -> { - // when - deletePurchaseTicketsUseCase.deletePurchaseTickets(deletePurchaseCommand); - // then - }).isInstanceOf(PurchaseNotFoundException.class); - } - - @Test - @DisplayName("๋นˆ ๊ตฌ๋งค ๋‚ด์—ญ > ํ‹ฐ์ผ“ ํ™˜๋ถˆ ์š”์ฒญ > ์‹คํŒจ") - @Transactional - void deletePurchaseTicketsFailEmptyPurchase() { - // given - var mockEmail = "test1@test.com"; - var member = testHelper.createMember(mockEmail, "1234"); - var ticketing = createTicketing(member, 0, 5); - var purchaseTicketPair = createPurchase(member, ticketing, 0); - var purchase = purchaseTicketPair.getFirst(); - - List ticketsToRefund = Collections.singletonList(UUID.randomUUID()); - var deletePurchaseCommand = DeletePurchaseTicketsCommandDto.builder() - .memberEmail(mockEmail) - .purchaseId(purchase.getId()) - .ticketIds(ticketsToRefund) - .build(); - - Assertions.assertThatThrownBy(() -> { - // when - deletePurchaseTicketsUseCase.deletePurchaseTickets(deletePurchaseCommand); - // then - }).isInstanceOf(EmptyPurchaseException.class); - } - - @Test - @DisplayName("๊ตฌ๋งค ๋‚ด์—ญ ์†Œ์œ ์ž๊ฐ€ ์•„๋‹˜ > ํ‹ฐ์ผ“ ํ™˜๋ถˆ ์š”์ฒญ > ์‹คํŒจ") - @Transactional - void deletePurchaseTicketsFailNotPurchaseOwner() { - // given - var mockEmail = "test1@test.com"; - var member = testHelper.createMember(mockEmail, "1234"); - var ticketing = createTicketing(member, 0, 5); - var purchaseTicketPair = createPurchase(member, ticketing, 1); - var purchase = purchaseTicketPair.getFirst(); - var tickets = purchaseTicketPair.getSecond(); - - List ticketsToRefund = Collections.singletonList(tickets.getFirst().getId()); - var deletePurchaseCommand = DeletePurchaseTicketsCommandDto.builder() - .memberEmail("another@test.com") - .purchaseId(purchase.getId()) - .ticketIds(ticketsToRefund) - .build(); - - Assertions.assertThatThrownBy(() -> { - // when - deletePurchaseTicketsUseCase.deletePurchaseTickets(deletePurchaseCommand); - // then - }).isInstanceOf(AccessForNotOwnedPurchaseException.class); - } - - @Test - @DisplayName("ํ‹ฐ์ผ“ํŒ… ํŒ๋งค ๊ธฐ๊ฐ„์ด ์•„๋‹˜ > ํ‹ฐ์ผ“ ํ™˜๋ถˆ ์š”์ฒญ > ์‹คํŒจ") - @Transactional - void deletePurchaseTicketsFailNotTicketingSalePeriod() { - // given - var mockEmail = "test1@test.com"; - var member = testHelper.createMember(mockEmail, "1234"); - var ticketing = createTicketing(member, -2, 1); - var purchaseTicketPair = createPurchase(member, ticketing, 1); - var purchase = purchaseTicketPair.getFirst(); - var tickets = purchaseTicketPair.getSecond(); - - List ticketsToRefund = Collections.singletonList(tickets.getFirst().getId()); - var deletePurchaseCommand = DeletePurchaseTicketsCommandDto.builder() - .memberEmail(mockEmail) - .purchaseId(purchase.getId()) - .ticketIds(ticketsToRefund) - .build(); - - Assertions.assertThatThrownBy(() -> { - // when - deletePurchaseTicketsUseCase.deletePurchaseTickets(deletePurchaseCommand); - // then - }).isInstanceOf(PurchaseNotInSalePeriodException.class); - } - - private Ticketing createTicketing(Member member, int saleStartAfterYears, int stock) { - var now = LocalDateTime.now(); - var eventTime = now.plusYears(saleStartAfterYears + 2); - var saleStart = now.plusYears(saleStartAfterYears); - var saleEnd = now.plusYears(saleStartAfterYears + 1); - var ticketing = ticketingRepository.save(Ticketing.builder() - .price(1000) - .title("test") - .member(member) - .description("") - .location("Seoul") - .eventTime(eventTime) - .saleStart(saleStart) - .saleEnd(saleEnd) - .category("concert") - .runningMinutes(300).build()); - ticketRepository.saveAll(Arrays.stream(new int[stock]) - .mapToObj(i -> Ticket.builder().ticketing(ticketing).build()) - .toList()); - return ticketing; - } - - private Pair> createPurchase(Member member, Ticketing ticketing, int count) { - var purchase = this.purchaseRepository.save(Purchase.builder().member(member).build()); - - if (count > 0) { - var tickets = updateTicketPurchase(purchase, ticketing, count); - return Pair.of(purchase, tickets); - } - return Pair.of(purchase, Collections.emptyList()); - } - - private List updateTicketPurchase(Purchase purchase, Ticketing ticketing, int count) { - var tickets = this.ticketRepository.findByTicketingIdAndPurchaseIsNullOrderById(ticketing.getId(), - Limit.of(count)); - if (tickets.size() < count) { - throw new NotEnoughTicketException(); - } - tickets.forEach(ticket -> { - ticket.setPurchase(purchase); - this.ticketRepository.save(ticket); - }); - return tickets; - } -} diff --git a/src/test/java/com/tiketeer/Tiketeer/domain/purchase/usecase/GetPurchaseTicketsUseCaseTest.java b/src/test/java/com/tiketeer/Tiketeer/domain/purchase/usecase/GetPurchaseTicketsUseCaseTest.java deleted file mode 100644 index 7167d4a0..00000000 --- a/src/test/java/com/tiketeer/Tiketeer/domain/purchase/usecase/GetPurchaseTicketsUseCaseTest.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.tiketeer.Tiketeer.domain.purchase.usecase; - -import static org.assertj.core.api.Assertions.*; - -import java.util.UUID; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -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; -import org.springframework.context.annotation.Import; -import org.springframework.transaction.annotation.Transactional; - -import com.tiketeer.Tiketeer.domain.purchase.PurchaseTestHelper; -import com.tiketeer.Tiketeer.domain.purchase.exception.AccessForNotOwnedPurchaseException; -import com.tiketeer.Tiketeer.domain.purchase.exception.PurchaseNotFoundException; -import com.tiketeer.Tiketeer.domain.purchase.usecase.dto.GetPurchaseTicketsCommandDto; -import com.tiketeer.Tiketeer.domain.ticketing.TicketingTestHelper; -import com.tiketeer.Tiketeer.testhelper.TestHelper; - -@Import({TestHelper.class, TicketingTestHelper.class, PurchaseTestHelper.class}) -@SpringBootTest -class GetPurchaseTicketsUseCaseTest { - @Autowired - private TestHelper testHelper; - @Autowired - private TicketingTestHelper ticketingTestHelper; - @Autowired - private PurchaseTestHelper purchaseTestHelper; - @Autowired - private GetPurchaseTicketsUseCase getPurchaseTicketsUseCase; - - @BeforeEach - void initTable() { - testHelper.initDB(); - } - - @AfterEach - void cleanTable() { - testHelper.cleanDB(); - } - - @Test - @DisplayName("ํ‹ฐ์ผ“ ๊ตฌ๋งค > ํ•˜๋‚˜์˜ ๊ตฌ๋งค ํ•˜์œ„ ํ‹ฐ์ผ“ ๋ชฉ๋ก ์กฐํšŒ > ์„ฑ๊ณต") - @Transactional - void getPurchaseTicketsSuccess() { - //given - var email = "test@test.com"; - var member = testHelper.createMember(email, "1q2w3e4r!!"); - var ticketing = ticketingTestHelper.createTicketing(member.getId(), 0, 10); - var purchaseTicketPair = purchaseTestHelper.createPurchase(member.getId(), ticketing.getId(), 5); - var purchase = purchaseTicketPair.getFirst(); - var tickets = purchaseTicketPair.getSecond(); - //when - var ticketIds = getPurchaseTicketsUseCase.getPurchaseTickets( - new GetPurchaseTicketsCommandDto(purchase.getId(), email)).getTicketIds(); - - //then - assertThat(ticketIds.size()).isEqualTo(5); - tickets.forEach(ticket -> { - assertThat(ticketIds.contains(ticket.getId())).isTrue(); - }); - - } - - @Test - @DisplayName("์กด์žฌํ•˜์ง€ ์•Š๋Š” ๊ตฌ๋งค > ํ•˜์œ„ ํ‹ฐ์ผ“ ๋ชฉ๋ก ์กฐํšŒ > ์‹คํŒจ") - @Transactional - void getPurchaseTicketsFailNoSuchPurchase() { - //given - var email = "test@test.com"; - testHelper.createMember(email, "1q2w3e4r!!"); - - assertThatThrownBy(() -> { - //when - getPurchaseTicketsUseCase.getPurchaseTickets(new GetPurchaseTicketsCommandDto(UUID.randomUUID(), email)); - //then - }).isInstanceOf(PurchaseNotFoundException.class); - - } - - @Test - @DisplayName("๊ตฌ๋งค ๋‚ด์—ญ ์†Œ์œ ์ž๊ฐ€ ์•„๋‹˜ > ํ‹ฐ์ผ“ ์กฐํšŒ ์š”์ฒญ > ์‹คํŒจ") - @Transactional - void getPurchaseTicketsFailNotPurchaseOwner() { - //given - var email = "test@test.com"; - var member = testHelper.createMember(email, "1q2w3e4r!!"); - var ticketing = ticketingTestHelper.createTicketing(member.getId(), 0, 10); - var purchaseTicketPair = purchaseTestHelper.createPurchase(member.getId(), ticketing.getId(), 5); - var purchase = purchaseTicketPair.getFirst(); - - assertThatThrownBy(() -> { - //when - getPurchaseTicketsUseCase.getPurchaseTickets( - new GetPurchaseTicketsCommandDto(purchase.getId(), "malory@test.com")); - //then - }).isInstanceOf(AccessForNotOwnedPurchaseException.class); - - } -} \ No newline at end of file diff --git a/src/test/java/com/tiketeer/Tiketeer/domain/ticket/controller/TicketControllerTest.java b/src/test/java/com/tiketeer/Tiketeer/domain/ticket/controller/TicketControllerTest.java new file mode 100644 index 00000000..0c1a368a --- /dev/null +++ b/src/test/java/com/tiketeer/Tiketeer/domain/ticket/controller/TicketControllerTest.java @@ -0,0 +1,161 @@ +package com.tiketeer.Tiketeer.domain.ticket.controller; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import java.util.List; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Import; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.tiketeer.Tiketeer.auth.constant.JwtMetadata; +import com.tiketeer.Tiketeer.domain.member.exception.MemberNotFoundException; +import com.tiketeer.Tiketeer.domain.member.repository.MemberRepository; +import com.tiketeer.Tiketeer.domain.role.constant.RoleEnum; +import com.tiketeer.Tiketeer.domain.ticket.controller.dto.PostTicketRequestDto; +import com.tiketeer.Tiketeer.domain.ticket.repository.TicketRepository; +import com.tiketeer.Tiketeer.domain.ticket.usecase.dto.CreateTicketMetadata; +import com.tiketeer.Tiketeer.domain.ticketing.TicketingTestHelper; +import com.tiketeer.Tiketeer.domain.ticketing.repository.TicketingRepository; +import com.tiketeer.Tiketeer.testhelper.TestHelper; + +import jakarta.servlet.http.Cookie; +import lombok.extern.slf4j.Slf4j; + +@Import({TestHelper.class, TicketingTestHelper.class}) +@SpringBootTest +@AutoConfigureMockMvc +@Slf4j +public class TicketControllerTest { + @Autowired + private MockMvc mockMvc; + @Autowired + private TestHelper testHelper; + @Autowired + private TicketingTestHelper ticketingTestHelper; + @Autowired + private TicketingRepository ticketingRepository; + @Autowired + private TicketRepository ticketRepository; + @Autowired + private ObjectMapper objectMapper; + @Autowired + private MemberRepository memberRepository; + + @BeforeEach + void initTable() { + testHelper.initDB(); + } + + @AfterEach + void cleanTable() { + testHelper.cleanDB(); + } + + @Test + @DisplayName("์ •์ƒ ์กฐ๊ฑด > ํ‹ฐ์ผ“ ์ƒ์„ฑ ์š”์ฒญ > ์„ฑ๊ณต") + @Transactional + void postTicketSuccess() throws Exception { + // given + var email = "seller@test.com"; + String token = testHelper.registerAndLoginAndReturnAccessToken(email, RoleEnum.SELLER); + var seller = memberRepository.findByEmail(email).orElseThrow(MemberNotFoundException::new); + var ticketing = ticketingTestHelper.createTicketing(seller.getId(), 0); + + Cookie cookie = new Cookie(JwtMetadata.ACCESS_TOKEN, token); + + var ticketStock = 10; + var title = "test"; + var price = 2000; + var tickets = List.of( + CreateTicketMetadata.builder() + .stock(ticketStock) + .remainingStock(ticketStock) + .title(title) + .description("") + .price(price) + .build() + ); + + var req = PostTicketRequestDto.builder() + .tickets(tickets) + .build(); + String content = objectMapper.writeValueAsString(req); + + // when + mockMvc + .perform(post("/api/ticketings/{ticketingId}/tickets", ticketing.getId()) + .contextPath("/api") + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding("utf-8") + .content(content) + .cookie(cookie) + ) + //then + .andExpect(status().isCreated()); + var ticketsInDb = ticketRepository.findAllByTicketing(ticketing); + Assertions.assertThat(ticketsInDb.size()).isEqualTo(1); + Assertions.assertThat(ticketsInDb.getFirst().getTitle()).isEqualTo(title); + Assertions.assertThat(ticketsInDb.getFirst().getStock()).isEqualTo(ticketStock); + Assertions.assertThat(ticketsInDb.getFirst().getPrice()).isEqualTo(price); + } + + @Test + @DisplayName("์ •์ƒ ์กฐ๊ฑด > ํ‹ฐ์ผ“ ์ˆ˜์ • ์š”์ฒญ > ์„ฑ๊ณต") + @Transactional + void putTicketSuccess() throws Exception { + // given + var email = "seller@test.com"; + String token = testHelper.registerAndLoginAndReturnAccessToken(email, RoleEnum.SELLER); + var seller = memberRepository.findByEmail(email).orElseThrow(MemberNotFoundException::new); + var ticketing = ticketingTestHelper.createTicketing(seller.getId(), 0, 1000, 5); + + Cookie cookie = new Cookie(JwtMetadata.ACCESS_TOKEN, token); + + var ticketStock = 10; + var title = "test"; + var price = 2000; + var tickets = List.of( + CreateTicketMetadata.builder() + .stock(ticketStock) + .remainingStock(ticketStock) + .title(title) + .description("") + .price(price) + .build() + ); + + var req = PostTicketRequestDto.builder() + .tickets(tickets) + .build(); + String content = objectMapper.writeValueAsString(req); + + // when + mockMvc + .perform(put("/api/ticketings/{ticketingId}/tickets", ticketing.getId()) + .contextPath("/api") + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding("utf-8") + .content(content) + .cookie(cookie) + ) + //then + .andExpect(status().isOk()); + var ticketsInDb = ticketRepository.findAllByTicketing(ticketing); + Assertions.assertThat(ticketsInDb.size()).isEqualTo(1); + Assertions.assertThat(ticketsInDb.getFirst().getTitle()).isEqualTo(title); + Assertions.assertThat(ticketsInDb.getFirst().getStock()).isEqualTo(ticketStock); + Assertions.assertThat(ticketsInDb.getFirst().getPrice()).isEqualTo(price); + } +} diff --git a/src/test/java/com/tiketeer/Tiketeer/domain/ticket/repository/TicketRepositoryTest.java b/src/test/java/com/tiketeer/Tiketeer/domain/ticket/repository/TicketRepositoryTest.java index b9d3d95e..fe23c1e8 100644 --- a/src/test/java/com/tiketeer/Tiketeer/domain/ticket/repository/TicketRepositoryTest.java +++ b/src/test/java/com/tiketeer/Tiketeer/domain/ticket/repository/TicketRepositoryTest.java @@ -12,7 +12,6 @@ import com.tiketeer.Tiketeer.domain.member.Member; import com.tiketeer.Tiketeer.domain.member.repository.MemberRepository; -import com.tiketeer.Tiketeer.domain.purchase.Purchase; import com.tiketeer.Tiketeer.domain.purchase.repository.PurchaseRepository; import com.tiketeer.Tiketeer.domain.role.Role; import com.tiketeer.Tiketeer.domain.role.constant.RoleEnum; @@ -38,81 +37,6 @@ public class TicketRepositoryTest { @Autowired private RoleRepository roleRepository; - @Test - @DisplayName("ํ‹ฐ์ผ“ ์ƒ์„ฑ ๋ฐ purchase_id ํ• ๋‹น > purchase_id๋กœ ์กฐํšŒ > ์„ฑ๊ณต") - void findAllByPurchase() { - // given - var role = roleRepository.save(new Role(RoleEnum.SELLER)); - var member = memberRepository.save( - new Member("test@gmail.com", "asdf1234", 0L, false, null, role)); - var now = LocalDateTime.now(); - var ticketing = ticketingRepository.save( - Ticketing.builder() - .price(1000) - .member(member) - .description("") - .title("test") - .location("Seoul") - .eventTime(now) - .category("") - .runningMinutes(600) - .saleStart(now) - .saleEnd(now) - .build() - ); - var purchase = purchaseRepository.save(new Purchase(member)); - ticketRepository.save( - new Ticket(purchase, ticketing)); - ticketRepository.save( - new Ticket(purchase, ticketing)); - - // when - var tickets = ticketRepository.findAllByPurchase(purchase); - - // then - assertThat(tickets.size()).isEqualTo(2); - assertThat(tickets.get(0).getTicketing()).isEqualTo(ticketing); - assertThat(tickets.get(0).getPurchase()).isEqualTo(purchase); - assertThat(tickets.get(1).getTicketing()).isEqualTo(ticketing); - assertThat(tickets.get(1).getPurchase()).isEqualTo(purchase); - } - - @Test - @DisplayName("ํ‹ฐ์ผ“ ์ƒ์„ฑ > ticketing_id๋กœ purchase_id๊ฐ€ ํ• ๋‹น๋˜์ง€ ์•Š์€ ํ‹ฐ์ผ“ ์ „์ฒด ์กฐํšŒ > ์„ฑ๊ณต") - void findByTicketingIdAndPurchaseIsNullOrderById() { - // given - var role = roleRepository.save(new Role(RoleEnum.SELLER)); - var member = memberRepository.save( - new Member("test@gmail.com", "asdf1234", 0L, false, null, role)); - var now = LocalDateTime.now(); - var ticketing = ticketingRepository.save( - Ticketing.builder() - .price(1000) - .member(member) - .description("") - .title("test") - .location("Seoul") - .eventTime(now) - .category("") - .runningMinutes(600) - .saleStart(now) - .saleEnd(now) - .build() - ); - ticketRepository.save( - Ticket.builder().ticketing(ticketing).build()); - ticketRepository.save( - Ticket.builder().ticketing(ticketing).build()); - - // when - var tickets = ticketRepository.findByTicketingIdAndPurchaseIsNull(ticketing.getId()); - - // then - assertThat(tickets.size()).isEqualTo(2); - assertThat(tickets.getFirst().getTicketing()).isEqualTo(ticketing); - assertThat(tickets.getFirst().getPurchase()).isNull(); - } - @Test @DisplayName("ํ‹ฐ์ผ“ ์ƒ์„ฑ > ticketing_id๋กœ purchase_id๊ฐ€ ํ• ๋‹น๋˜์ง€ ์•Š์€ ํ‹ฐ์ผ“ n๊ฐœ ์กฐํšŒ > ์„ฑ๊ณต") void findByTicketingIdAndPurchaseIsNullOrderByIdWithLimit() { @@ -123,7 +47,6 @@ void findByTicketingIdAndPurchaseIsNullOrderByIdWithLimit() { var now = LocalDateTime.now(); var ticketing = ticketingRepository.save( Ticketing.builder() - .price(1000) .member(member) .description("") .title("test") @@ -136,16 +59,15 @@ void findByTicketingIdAndPurchaseIsNullOrderByIdWithLimit() { .build() ); ticketRepository.save( - Ticket.builder().ticketing(ticketing).build()); + Ticket.builder().ticketing(ticketing).title("ticket1").price(1000).stock(10).remainingStock(10).build()); ticketRepository.save( - Ticket.builder().ticketing(ticketing).build()); + Ticket.builder().ticketing(ticketing).title("ticket2").price(1000).stock(10).remainingStock(10).build()); // when - var tickets = ticketRepository.findByTicketingIdAndPurchaseIsNullOrderById(ticketing.getId(), Limit.of(1)); + var tickets = ticketRepository.findByTicketingIdOrderById(ticketing.getId(), Limit.of(1)); // then assertThat(tickets.size()).isEqualTo(1); assertThat(tickets.getFirst().getTicketing()).isEqualTo(ticketing); - assertThat(tickets.getFirst().getPurchase()).isNull(); } } diff --git a/src/test/java/com/tiketeer/Tiketeer/domain/ticket/service/TicketCrudServiceTest.java b/src/test/java/com/tiketeer/Tiketeer/domain/ticket/service/TicketCrudServiceTest.java index f923516a..9fb76147 100644 --- a/src/test/java/com/tiketeer/Tiketeer/domain/ticket/service/TicketCrudServiceTest.java +++ b/src/test/java/com/tiketeer/Tiketeer/domain/ticket/service/TicketCrudServiceTest.java @@ -1,7 +1,9 @@ package com.tiketeer.Tiketeer.domain.ticket.service; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.UUID; import org.assertj.core.api.Assertions; @@ -15,30 +17,25 @@ import org.springframework.transaction.annotation.Transactional; import com.tiketeer.Tiketeer.domain.member.Member; -import com.tiketeer.Tiketeer.domain.member.repository.MemberRepository; -import com.tiketeer.Tiketeer.domain.role.repository.RoleRepository; import com.tiketeer.Tiketeer.domain.ticket.Ticket; import com.tiketeer.Tiketeer.domain.ticket.repository.TicketRepository; +import com.tiketeer.Tiketeer.domain.ticket.usecase.dto.CreateTicketMetadata; import com.tiketeer.Tiketeer.domain.ticketing.Ticketing; +import com.tiketeer.Tiketeer.domain.ticketing.TicketingTestHelper; import com.tiketeer.Tiketeer.domain.ticketing.exception.TicketingNotFoundException; import com.tiketeer.Tiketeer.domain.ticketing.repository.TicketingRepository; -import com.tiketeer.Tiketeer.domain.ticketing.service.TicketingService; import com.tiketeer.Tiketeer.testhelper.TestHelper; -@Import({TestHelper.class}) +@Import({TestHelper.class, TicketingTestHelper.class}) @SpringBootTest @DisplayName("TicketService ํ…Œ์ŠคํŠธ") public class TicketCrudServiceTest { @Autowired private TestHelper testHelper; @Autowired - private TicketCrudService ticketCrudService; - @Autowired - private TicketingService ticketingService; + private TicketingTestHelper ticketingTestHelper; @Autowired - private MemberRepository memberRepository; - @Autowired - private RoleRepository roleRepository; + private TicketCrudService ticketCrudService; @Autowired private TicketRepository ticketRepository; @Autowired @@ -77,16 +74,17 @@ void listTicketByTicketingSuccess() { var mockEmail = "test@test.com"; var member = testHelper.createMember(mockEmail); + var mockTicket = 5; var mockStock = 30; - var ticketingId = createTicketingAndReturnId(member, mockStock, now.plusYears(1), now.plusYears(2), - now.plusYears(3)); + var ticketing = createTicketing(member, now.plusYears(1), now.plusYears(2), now.plusYears(3)); + createTickets(ticketing, mockTicket, mockStock, mockStock, "", "", 1000); // when - var tickets = ticketCrudService.listTicketByTicketingId(ticketingId); + var tickets = ticketCrudService.listTicketByTicketingId(ticketing.getId()); // then - Assertions.assertThat(tickets.size()).isEqualTo(mockStock); + Assertions.assertThat(tickets.size()).isEqualTo(mockTicket); } @Test @@ -97,13 +95,13 @@ void createTicketsFailBecauseNotExistTicketing() { Assertions.assertThatThrownBy(() -> { // when - ticketCrudService.createTickets(invalidTicketingId, 100); + ticketCrudService.createTickets(invalidTicketingId, new ArrayList<>()); // then }).isInstanceOf(TicketingNotFoundException.class); } @Test - @DisplayName("์œ ํšจํ•œ ํ‹ฐ์ผ€ํŒ… (๊ธฐ์กด ํ‹ฐ์ผ“ 10) > ์ถ”๊ฐ€ ํ•˜์œ„ ํ‹ฐ์ผ“ ์ƒ์„ฑ ์š”์ฒญ (20) > ์„ฑ๊ณต ๋ฐ ์ด ์žฌ๊ณ  10 + 20") + @DisplayName("์œ ํšจํ•œ ํ‹ฐ์ผ€ํŒ… (ํ‹ฐ์ผ“ 10์ข…๋ฅ˜ * 10์žฅ) > ์ถ”๊ฐ€ ํ•˜์œ„ ํ‹ฐ์ผ“ ์ƒ์„ฑ ์š”์ฒญ (1์ข…๋ฅ˜ 20์žฅ) > ์„ฑ๊ณต ๋ฐ ์ด ์žฌ๊ณ  10*10 + 20") void createTicketsSuccess() { // given var now = LocalDateTime.now(); @@ -112,34 +110,57 @@ void createTicketsSuccess() { var member = testHelper.createMember(mockEmail); var mockStock = 10; - var ticketingId = createTicketingAndReturnId(member, mockStock, now.plusYears(1), now.plusYears(2), - now.plusYears(3)); + var ticketing = createTicketing(member, now.plusYears(1), now.plusYears(2), now.plusYears(3)); + createTickets(ticketing, 10, mockStock, mockStock, "", "", 1000); var addTickets = 20; // when - ticketCrudService.createTickets(ticketingId, addTickets); + ticketCrudService.createTickets(ticketing.getId(), Collections.singletonList( + CreateTicketMetadata.builder() + .stock(addTickets) + .remainingStock(addTickets) + .title("") + .description("") + .price(2000) + .build() + )); // then - var tickets = ticketCrudService.listTicketByTicketingId(ticketingId); - Assertions.assertThat(tickets.size()).isEqualTo(mockStock + addTickets); + var tickets = ticketCrudService.listTicketByTicketingId(ticketing.getId()); + Assertions.assertThat(tickets.size()).isEqualTo(mockStock + 1); + + var ticketCount = tickets.stream().mapToInt(Ticket::getStock).sum(); + Assertions.assertThat(ticketCount).isEqualTo(120); } - private UUID createTicketingAndReturnId(Member member, int stock, LocalDateTime saleStart, LocalDateTime saleEnd, + private Ticketing createTicketing(Member member, LocalDateTime saleStart, + LocalDateTime saleEnd, LocalDateTime eventTime) { - var ticketing = ticketingRepository.save(Ticketing.builder() + return ticketingRepository.save(Ticketing.builder() .member(member) .title("์Œ์•…ํšŒ") .location("์„œ์šธ ๊ฐ•๋‚จ์—ญ 8๋ฒˆ ์ถœ๊ตฌ") .category("์Œ์•…ํšŒ") .runningMinutes(100) - .price(10000L) .saleStart(saleStart) .saleEnd(saleEnd) .eventTime(eventTime).build()); - ticketRepository.saveAll(Arrays.stream(new int[stock]) - .mapToObj(i -> Ticket.builder().ticketing(ticketing).build()) + + } + + private void createTickets(Ticketing ticketing, int distinct, int stock, int remainingStock, String title, + String description, + long price) { + ticketRepository.saveAll(Arrays.stream(new int[distinct]) + .mapToObj(i -> Ticket.builder() + .ticketing(ticketing) + .price(price) + .title(title) + .description(description) + .stock(stock) + .remainingStock(remainingStock) + .build()) .toList()); - return ticketing.getId(); } } diff --git a/src/test/java/com/tiketeer/Tiketeer/domain/ticket/service/TicketStockServiceTest.java b/src/test/java/com/tiketeer/Tiketeer/domain/ticket/service/TicketStockServiceTest.java new file mode 100644 index 00000000..c27e2c28 --- /dev/null +++ b/src/test/java/com/tiketeer/Tiketeer/domain/ticket/service/TicketStockServiceTest.java @@ -0,0 +1,113 @@ +package com.tiketeer.Tiketeer.domain.ticket.service; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +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; +import org.springframework.context.annotation.Import; +import org.springframework.transaction.annotation.Transactional; + +import com.tiketeer.Tiketeer.domain.ticket.exception.InvalidRemainingStockException; +import com.tiketeer.Tiketeer.domain.ticket.exception.NotEnoughRemainingStockException; +import com.tiketeer.Tiketeer.domain.ticket.repository.TicketRepository; +import com.tiketeer.Tiketeer.domain.ticketing.TicketingTestHelper; +import com.tiketeer.Tiketeer.testhelper.TestHelper; + +@Import({TestHelper.class, TicketingTestHelper.class}) +@SpringBootTest +@DisplayName("TicketStockService ํ…Œ์ŠคํŠธ") +public class TicketStockServiceTest { + @Autowired + private TestHelper testHelper; + @Autowired + private TicketingTestHelper ticketingTestHelper; + @Autowired + private TicketStockService ticketStockService; + @Autowired + private TicketRepository ticketRepository; + + @BeforeEach + void initTable() { + testHelper.initDB(); + } + + @AfterEach + void cleanTable() { + testHelper.cleanDB(); + } + + @Test + @DisplayName("ํ‹ฐ์ผ“ ์žฌ๊ณ  ์ถฉ๋ถ„ > ํ‹ฐ์ผ“ ์žฌ๊ณ  ๊ฐ์†Œ ์š”์ฒญ > ์„ฑ๊ณต") + @Transactional + void subtractRemainingStockSuccess() { + // given + var mockEmail = "test@test.com"; + var member = testHelper.createMember(mockEmail); + var ticketing = ticketingTestHelper.createTicketing(member.getId(), 0, 2); + var ticketId = ticketRepository.findAll().getFirst().getId(); + + // when + ticketStockService.subtractRemainingStock(ticketId, 1); + + // then + var ticketInDb = ticketRepository.findById(ticketId).orElseThrow(); + Assertions.assertThat(ticketInDb.getRemainingStock()).isEqualTo(1); + } + + @Test + @DisplayName("ํ‹ฐ์ผ“ ์žฌ๊ณ  ๋ถ€์กฑ > ํ‹ฐ์ผ“ ์žฌ๊ณ  ๊ฐ์†Œ ์š”์ฒญ > ์‹คํŒจ") + @Transactional + void subtractRemainingStockFailNotEnoughRemainingStock() { + // given + var mockEmail = "test@test.com"; + var member = testHelper.createMember(mockEmail); + var ticketing = ticketingTestHelper.createTicketing(member.getId(), 0, 2); + var ticketId = ticketRepository.findAll().getFirst().getId(); + + Assertions.assertThatThrownBy(() -> { + // when + ticketStockService.subtractRemainingStock(ticketId, 3); + // then + }).isInstanceOf(NotEnoughRemainingStockException.class); + } + + @Test + @DisplayName("ํ‹ฐ์ผ“ ์žฌ๊ณ  ๋ถ€์กฑ > ํ‹ฐ์ผ“ ์žฌ๊ณ  ์ฆ๊ฐ€ ์š”์ฒญ > ์„ฑ๊ณต") + @Transactional + void addRemainingStockSuccess() { + // given + var mockEmail = "test@test.com"; + var member = testHelper.createMember(mockEmail); + var ticketing = ticketingTestHelper.createTicketing(member.getId(), 0, 2); + var ticket = ticketRepository.findAll().getFirst(); + ticket.setRemainingStock(0); + + // when + ticketStockService.addRemainingStock(ticket.getId(), 1); + + // then + var ticketInDb = ticketRepository.findById(ticket.getId()).orElseThrow(); + Assertions.assertThat(ticketInDb.getRemainingStock()).isEqualTo(1); + } + + @Test + @DisplayName("ํ‹ฐ์ผ“ ์žฌ๊ณ  ์ถฉ๋ถ„ > ํ‹ฐ์ผ“ ์žฌ๊ณ  ์ฆ๊ฐ€ ์š”์ฒญ > ์‹คํŒจ") + @Transactional + void addRemainingStockFailInvalidRemainingStock() { + // given + var mockEmail = "test@test.com"; + var member = testHelper.createMember(mockEmail); + var ticketing = ticketingTestHelper.createTicketing(member.getId(), 0, 2); + var ticketId = ticketRepository.findAll().getFirst().getId(); + + Assertions.assertThatThrownBy(() -> { + // when + ticketStockService.addRemainingStock(ticketId, 1); + // then + }).isInstanceOf(InvalidRemainingStockException.class); + } + +} diff --git a/src/test/java/com/tiketeer/Tiketeer/domain/ticket/usecase/CreateTicketUseCaseTest.java b/src/test/java/com/tiketeer/Tiketeer/domain/ticket/usecase/CreateTicketUseCaseTest.java new file mode 100644 index 00000000..4e36f421 --- /dev/null +++ b/src/test/java/com/tiketeer/Tiketeer/domain/ticket/usecase/CreateTicketUseCaseTest.java @@ -0,0 +1,142 @@ +package com.tiketeer.Tiketeer.domain.ticket.usecase; + +import java.util.List; +import java.util.UUID; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +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; +import org.springframework.context.annotation.Import; + +import com.tiketeer.Tiketeer.domain.member.repository.MemberRepository; +import com.tiketeer.Tiketeer.domain.ticket.repository.TicketRepository; +import com.tiketeer.Tiketeer.domain.ticket.usecase.dto.CreateTicketCommandDto; +import com.tiketeer.Tiketeer.domain.ticket.usecase.dto.CreateTicketMetadata; +import com.tiketeer.Tiketeer.domain.ticketing.TicketingTestHelper; +import com.tiketeer.Tiketeer.domain.ticketing.exception.ModifyForNotOwnedTicketingException; +import com.tiketeer.Tiketeer.domain.ticketing.exception.TicketingNotFoundException; +import com.tiketeer.Tiketeer.domain.ticketing.repository.TicketingRepository; +import com.tiketeer.Tiketeer.testhelper.TestHelper; + +@Import({TestHelper.class, TicketingTestHelper.class}) +@SpringBootTest +public class CreateTicketUseCaseTest { + @Autowired + private TestHelper testHelper; + @Autowired + private TicketingTestHelper ticketingTestHelper; + @Autowired + private TicketingRepository ticketingRepository; + @Autowired + private TicketRepository ticketRepository; + @Autowired + private MemberRepository memberRepository; + @Autowired + private CreateTicketUseCase createTicketUseCase; + + @BeforeEach + void initTable() { + testHelper.initDB(); + } + + @AfterEach + void cleanTable() { + testHelper.cleanDB(); + } + + @Test + @DisplayName("์ •์ƒ ์กฐ๊ฑด > ํ‹ฐ์ผ“ ์ถ”๊ฐ€ ์š”์ฒญ > ์„ฑ๊ณต") + void createTicketSuccess() { + // given + var email = "test@test.com"; + var member = testHelper.createMember(email); + var ticketing = ticketingTestHelper.createTicketing(member.getId(), 1); + var tickets = List.of( + CreateTicketMetadata.builder() + .stock(10) + .remainingStock(10) + .title("test") + .description("") + .price(2000) + .build() + ); + + // when + var command = CreateTicketCommandDto.builder() + .memberEmail(email) + .ticketingId(ticketing.getId()) + .createTicketMetadataList(tickets) + .build(); + createTicketUseCase.createTicket(command); + + // then + var ticketsInDb = ticketRepository.findAllByTicketing(ticketing); + Assertions.assertThat(ticketsInDb.size()).isEqualTo(1); + Assertions.assertThat(ticketsInDb.getFirst().getTitle()).isEqualTo("test"); + Assertions.assertThat(ticketsInDb.getFirst().getStock()).isEqualTo(10); + Assertions.assertThat(ticketsInDb.getFirst().getPrice()).isEqualTo(2000); + } + + @Test + @DisplayName("์กด์žฌํ•˜์ง€ ์•Š๋Š” ํ‹ฐ์ผ€ํŒ… > ํ‹ฐ์ผ“ ์ถ”๊ฐ€ ์š”์ฒญ > ์‹คํŒจ") + void createTicketFailBecauseTicketingNotExist() { + // given + var email = "test@test.com"; + var member = testHelper.createMember(email); + var tickets = List.of( + CreateTicketMetadata.builder() + .stock(10) + .remainingStock(10) + .title("test") + .description("") + .price(2000) + .build() + ); + + var command = CreateTicketCommandDto.builder() + .memberEmail(email) + .ticketingId(UUID.randomUUID()) + .createTicketMetadataList(tickets) + .build(); + Assertions.assertThatThrownBy(() -> { + // when + createTicketUseCase.createTicket(command); + //then + }).isInstanceOf(TicketingNotFoundException.class); + } + + @Test + @DisplayName("ํ‹ฐ์ผ€ํŒ… owner๊ฐ€ ์•„๋‹˜ > ํ‹ฐ์ผ“ ์ถ”๊ฐ€ ์š”์ฒญ > ์‹คํŒจ") + void createTicketFailBecauseNotOwnedTicketing() { + // given + var email = "test@test.com"; + var member = testHelper.createMember(email); + var ticketingOwner = testHelper.createMember("owner@test.com"); + var ticketing = ticketingTestHelper.createTicketing(ticketingOwner.getId(), 1); + var tickets = List.of( + CreateTicketMetadata.builder() + .stock(10) + .remainingStock(10) + .title("test") + .description("") + .price(2000) + .build() + ); + + // when + var command = CreateTicketCommandDto.builder() + .memberEmail(email) + .ticketingId(ticketing.getId()) + .createTicketMetadataList(tickets) + .build(); + Assertions.assertThatThrownBy(() -> { + // when + createTicketUseCase.createTicket(command); + // then + }).isInstanceOf(ModifyForNotOwnedTicketingException.class); + } +} diff --git a/src/test/java/com/tiketeer/Tiketeer/domain/ticket/usecase/UpdateTicketUseCaseTest.java b/src/test/java/com/tiketeer/Tiketeer/domain/ticket/usecase/UpdateTicketUseCaseTest.java new file mode 100644 index 00000000..bdc3f83e --- /dev/null +++ b/src/test/java/com/tiketeer/Tiketeer/domain/ticket/usecase/UpdateTicketUseCaseTest.java @@ -0,0 +1,158 @@ +package com.tiketeer.Tiketeer.domain.ticket.usecase; + +import java.util.List; +import java.util.UUID; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +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; +import org.springframework.context.annotation.Import; + +import com.tiketeer.Tiketeer.domain.member.repository.MemberRepository; +import com.tiketeer.Tiketeer.domain.ticket.Ticket; +import com.tiketeer.Tiketeer.domain.ticket.repository.TicketRepository; +import com.tiketeer.Tiketeer.domain.ticket.usecase.dto.CreateTicketMetadata; +import com.tiketeer.Tiketeer.domain.ticket.usecase.dto.UpdateTicketCommandDto; +import com.tiketeer.Tiketeer.domain.ticketing.TicketingTestHelper; +import com.tiketeer.Tiketeer.domain.ticketing.exception.ModifyForNotOwnedTicketingException; +import com.tiketeer.Tiketeer.domain.ticketing.exception.TicketingNotFoundException; +import com.tiketeer.Tiketeer.domain.ticketing.repository.TicketingRepository; +import com.tiketeer.Tiketeer.testhelper.TestHelper; + +@Import({TestHelper.class, TicketingTestHelper.class}) +@SpringBootTest +public class UpdateTicketUseCaseTest { + @Autowired + private TestHelper testHelper; + @Autowired + private TicketingTestHelper ticketingTestHelper; + @Autowired + private TicketingRepository ticketingRepository; + @Autowired + private TicketRepository ticketRepository; + @Autowired + private MemberRepository memberRepository; + @Autowired + private UpdateTicketUseCase updateTicketUseCase; + + @BeforeEach + void initTable() { + testHelper.initDB(); + } + + @AfterEach + void cleanTable() { + testHelper.cleanDB(); + } + + @Test + @DisplayName("์ •์ƒ ์กฐ๊ฑด > ํ‹ฐ์ผ“ ์ˆ˜์ • ์š”์ฒญ > ์„ฑ๊ณต") + void createTicketSuccess() { + // given + var email = "test@test.com"; + var member = testHelper.createMember(email); + var ticketing = ticketingTestHelper.createTicketing(member.getId(), 1); + var ticket1 = ticketRepository.save(new Ticket(1000, 10, 10, "ticket1", "", ticketing)); + var ticket2 = ticketRepository.save(new Ticket(1000, 10, 10, "ticket2", "", ticketing)); + + // when + var newTicket = new Ticket(500, 1, 1, "ticket3", "", ticketing); + var tickets = List.of( + CreateTicketMetadata.builder() + .stock(ticket1.getStock()) + .remainingStock(ticket1.getRemainingStock()) + .title(ticket1.getTitle()) + .description(ticket1.getDescription()) + .price(ticket1.getPrice()) + .build(), + CreateTicketMetadata.builder() + .stock(newTicket.getStock()) + .remainingStock(newTicket.getRemainingStock()) + .title(newTicket.getTitle()) + .price(newTicket.getPrice()) + .build() + ); + var command = UpdateTicketCommandDto.builder() + .memberEmail(email) + .ticketingId(ticketing.getId()) + .createTicketMetadataList(tickets) + .build(); + updateTicketUseCase.updateTicket(command); + + // then + var ticketsInDb = ticketRepository.findAllByTicketing(ticketing); + Assertions.assertThat(ticketsInDb.size()).isEqualTo(2); + Assertions.assertThat(ticketsInDb) + .extracting("title") + .containsExactlyInAnyOrder(ticket1.getTitle(), newTicket.getTitle()); + Assertions.assertThat(ticketsInDb) + .extracting("stock") + .containsExactlyInAnyOrder(ticket1.getStock(), newTicket.getStock()); + Assertions.assertThat(ticketsInDb) + .extracting("price") + .containsExactlyInAnyOrder(ticket1.getPrice(), newTicket.getPrice()); + } + + @Test + @DisplayName("์กด์žฌํ•˜์ง€ ์•Š๋Š” ํ‹ฐ์ผ€ํŒ… > ํ‹ฐ์ผ“ ์ˆ˜์ • ์š”์ฒญ > ์‹คํŒจ") + void createTicketFailBecauseTicketingNotExist() { + // given + var email = "test@test.com"; + var member = testHelper.createMember(email); + var tickets = List.of( + CreateTicketMetadata.builder() + .stock(10) + .remainingStock(10) + .title("test") + .description("") + .price(2000) + .build() + ); + + var command = UpdateTicketCommandDto.builder() + .memberEmail(email) + .ticketingId(UUID.randomUUID()) + .createTicketMetadataList(tickets) + .build(); + Assertions.assertThatThrownBy(() -> { + // when + updateTicketUseCase.updateTicket(command); + //then + }).isInstanceOf(TicketingNotFoundException.class); + } + + @Test + @DisplayName("ํ‹ฐ์ผ€ํŒ… owner๊ฐ€ ์•„๋‹˜ > ํ‹ฐ์ผ“ ์ˆ˜์ • ์š”์ฒญ > ์‹คํŒจ") + void createTicketFailBecauseNotOwnedTicketing() { + // given + var email = "test@test.com"; + var member = testHelper.createMember(email); + var ticketingOwner = testHelper.createMember("owner@test.com"); + var ticketing = ticketingTestHelper.createTicketing(ticketingOwner.getId(), 1); + var tickets = List.of( + CreateTicketMetadata.builder() + .stock(10) + .remainingStock(10) + .title("test") + .description("") + .price(2000) + .build() + ); + + // when + var command = UpdateTicketCommandDto.builder() + .memberEmail(email) + .ticketingId(ticketing.getId()) + .createTicketMetadataList(tickets) + .build(); + Assertions.assertThatThrownBy(() -> { + // when + updateTicketUseCase.updateTicket(command); + // then + }).isInstanceOf(ModifyForNotOwnedTicketingException.class); + } +} diff --git a/src/test/java/com/tiketeer/Tiketeer/domain/ticketing/TicketingTestHelper.java b/src/test/java/com/tiketeer/Tiketeer/domain/ticketing/TicketingTestHelper.java index 3f2ed884..af86abf3 100644 --- a/src/test/java/com/tiketeer/Tiketeer/domain/ticketing/TicketingTestHelper.java +++ b/src/test/java/com/tiketeer/Tiketeer/domain/ticketing/TicketingTestHelper.java @@ -1,7 +1,6 @@ package com.tiketeer.Tiketeer.domain.ticketing; import java.time.LocalDateTime; -import java.util.Arrays; import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; @@ -23,18 +22,13 @@ public class TicketingTestHelper { @Autowired private MemberRepository memberRepository; - public Ticketing createTicketing(UUID memberId, int saleStartAfterYears, int stock) { - return createTicketing(memberId, saleStartAfterYears, 1000, stock); - } - - public Ticketing createTicketing(UUID memberId, int saleStartAfterYears, int price, int stock) { + public Ticketing createTicketing(UUID memberId, int saleStartAfterYears) { var now = LocalDateTime.now(); var eventTime = now.plusYears(saleStartAfterYears + 2); var saleStart = now.plusYears(saleStartAfterYears); var saleEnd = now.plusYears(saleStartAfterYears + 1); var member = memberRepository.findById(memberId).orElseThrow(MemberNotFoundException::new); - var ticketing = ticketingRepository.save(Ticketing.builder() - .price(price) + return ticketingRepository.save(Ticketing.builder() .title("test") .member(member) .description("") @@ -44,9 +38,22 @@ public Ticketing createTicketing(UUID memberId, int saleStartAfterYears, int pri .saleEnd(saleEnd) .category("concert") .runningMinutes(300).build()); - ticketRepository.saveAll(Arrays.stream(new int[stock]) - .mapToObj(i -> Ticket.builder().ticketing(ticketing).build()) - .toList()); + } + + public Ticketing createTicketing(UUID memberId, int saleStartAfterYears, int stock) { + return createTicketing(memberId, saleStartAfterYears, 1000, stock); + } + + public Ticketing createTicketing(UUID memberId, int saleStartAfterYears, int price, int stock) { + var ticketing = createTicketing(memberId, saleStartAfterYears); + ticketRepository.save( + Ticket.builder() + .price(price) + .stock(stock) + .remainingStock(stock) + .title("ticket1") + .ticketing(ticketing) + .build()); return ticketing; } diff --git a/src/test/java/com/tiketeer/Tiketeer/domain/ticketing/controller/TicketingControllerTest.java b/src/test/java/com/tiketeer/Tiketeer/domain/ticketing/controller/TicketingControllerTest.java index 4bc2cd77..c7d3274f 100644 --- a/src/test/java/com/tiketeer/Tiketeer/domain/ticketing/controller/TicketingControllerTest.java +++ b/src/test/java/com/tiketeer/Tiketeer/domain/ticketing/controller/TicketingControllerTest.java @@ -84,9 +84,7 @@ void getAllTicketingsSuccess() throws Exception { var member = testHelper.createMember("user@example.com", RoleEnum.SELLER); var ticketingCnt = 3; var ticketingsInDb = createTicketings(member, ticketingCnt); - ticketingsInDb.forEach(ticketing -> { - createTickets(ticketing, 3); - }); + ticketingsInDb.forEach(this::createTickets); // when AtomicReference result = new AtomicReference<>(); @@ -125,7 +123,7 @@ void getTicketingSuccess() throws Exception { var member = testHelper.createMember("user@example.com", RoleEnum.SELLER); var ticketingInDb = createTicketings(member, 1).getFirst(); var stock = 10; - createTickets(ticketingInDb, stock); + createTickets(ticketingInDb); // when AtomicReference result = new AtomicReference<>(); @@ -148,8 +146,8 @@ void getTicketingSuccess() throws Exception { result.get().andReturn().getResponse().getContentAsString(), GetTicketingResponseDto.class); var ticketing = apiResponse.getData(); Assertions.assertThat(ticketing.getTitle()).isEqualTo("0"); - Assertions.assertThat(ticketing.getStock()).isEqualTo(stock); - Assertions.assertThat(ticketing.getRemainStock()).isEqualTo(stock); + // Assertions.assertThat(ticketing.getStock()).isEqualTo(stock); + // Assertions.assertThat(ticketing.getRemainStock()).isEqualTo(stock); Assertions.assertThat(ticketing.getOwner()).isEqualTo(member.getEmail()); } @@ -166,8 +164,6 @@ void postTicketingSuccess() throws Exception { .location("์„œ์šธ ๊ฐ•๋‚จ์—ญ 8๋ฒˆ ์ถœ๊ตฌ") .category("์Œ์•…ํšŒ") .runningMinutes(100) - .price(10000L) - .stock(20) .eventTime(now.plusYears(3)) .saleStart(now.plusYears(1)) .saleEnd(now.plusYears(2)) @@ -206,21 +202,17 @@ void patchTicketingSuccess() throws Exception { var title = "์Œ์•…ํšŒ"; var runningMinutes = 100; - var stock = 20; - var createTicketingCmd = createCreateTicketingCommand(email, title, runningMinutes, stock); + var createTicketingCmd = createCreateTicketingCommand(email, title, runningMinutes); var ticketingId = createTicketingUseCase.createTicketing(createTicketingCmd).getTicketingId(); var updatedTitle = "์Œ์•…ํšŒ1"; var updatedRunningMinutes = 120; - var updatedStock = 50; var req = PatchTicketingRequestDto.builder() .title(updatedTitle) .description(createTicketingCmd.getDescription()) .location(createTicketingCmd.getLocation()) .category(createTicketingCmd.getCategory()) .runningMinutes(updatedRunningMinutes) - .price(createTicketingCmd.getPrice()) - .stock(updatedStock) .eventTime(createTicketingCmd.getEventTime()) .saleStart(createTicketingCmd.getSaleStart()) .saleEnd(createTicketingCmd.getSaleEnd()) @@ -239,15 +231,12 @@ void patchTicketingSuccess() throws Exception { ).andExpect(status().is2xxSuccessful()); // then - var ticketingInfoList = ticketingRepository.findTicketingWithTicketStock(email); - Assertions.assertThat(ticketingInfoList.size()).isEqualTo(1); + var ticketingInfoOpt = ticketingRepository.findById(ticketingId); - var ticketingInfo = ticketingInfoList.getFirst(); - Assertions.assertThat(ticketingInfo.getTicketingId()).isEqualTo(ticketingId); - Assertions.assertThat(ticketingInfo.getPrice()).isEqualTo(createTicketingCmd.getPrice()); + var ticketingInfo = ticketingInfoOpt.get(); + Assertions.assertThat(ticketingInfo.getId()).isEqualTo(ticketingId); Assertions.assertThat(ticketingInfo.getCategory()).isEqualTo(createTicketingCmd.getCategory()); Assertions.assertThat(ticketingInfo.getTitle()).isEqualTo(updatedTitle); - Assertions.assertThat(ticketingInfo.getRemainStock()).isEqualTo(updatedStock); Assertions.assertThat(ticketingInfo.getRunningMinutes()).isEqualTo(updatedRunningMinutes); } @@ -261,8 +250,7 @@ void deleteTicketingSuccess() throws Exception { var title = "์Œ์•…ํšŒ"; var runningMinutes = 100; - var stock = 20; - var createTicketingCmd = createCreateTicketingCommand(email, title, runningMinutes, stock); + var createTicketingCmd = createCreateTicketingCommand(email, title, runningMinutes); var ticketingId = createTicketingUseCase.createTicketing(createTicketingCmd).getTicketingId(); // when @@ -291,7 +279,6 @@ private List createTicketings(Member member, int count) { .location("์„œ์šธ") .category("์ฝ˜์„œํŠธ") .runningMinutes(100) - .price(10000) .eventTime(LocalDateTime.now().plusMonths(2)) .saleStart(LocalDateTime.now().minusMonths(1)) .saleEnd(LocalDateTime.now().plusMonths(1)) @@ -300,26 +287,28 @@ private List createTicketings(Member member, int count) { return ticketingRepository.saveAll(ticketings); } - private List createTickets(Ticketing ticketing, int stock) { - return ticketRepository.saveAll(Arrays.stream(new int[stock]) - .mapToObj(i -> Ticket.builder().ticketing(ticketing).build()) - .toList()); + private List createTickets(Ticketing ticketing) { + return ticketRepository.saveAll( + Arrays.asList( + new Ticket(1000, 10, 10, "Bronze", "", ticketing), + new Ticket(2000, 10, 10, "Silver", "", ticketing), + new Ticket(3000, 10, 10, "Gold", "", ticketing) + ) + ); } - private CreateTicketingCommandDto createCreateTicketingCommand(String email, String title, int runningMinutes, - int stock) { + private CreateTicketingCommandDto createCreateTicketingCommand(String email, String title, int runningMinutes) { var now = LocalDateTime.now(); return CreateTicketingCommandDto.builder() .memberEmail(email) .title(title) - .price(1000L) .category("์นดํ…Œ๊ณ ๋ฆฌ") .location("์„œ์šธ") - .stock(stock) .runningMinutes(runningMinutes) .saleStart(now.plusYears(1)) .saleEnd(now.plusYears(2)) - .eventTime(now.plusYears(3)).build(); + .eventTime(now.plusYears(3)) + .build(); } private void wrapWithTimeConsole(Runnable runnable) { diff --git a/src/test/java/com/tiketeer/Tiketeer/domain/ticketing/repository/TicketingRepositoryTest.java b/src/test/java/com/tiketeer/Tiketeer/domain/ticketing/repository/TicketingRepositoryTest.java index 7e9e43cd..b5c5fe07 100644 --- a/src/test/java/com/tiketeer/Tiketeer/domain/ticketing/repository/TicketingRepositoryTest.java +++ b/src/test/java/com/tiketeer/Tiketeer/domain/ticketing/repository/TicketingRepositoryTest.java @@ -4,6 +4,7 @@ import static org.assertj.core.api.Assertions.*; import java.time.temporal.ChronoUnit; +import java.util.Arrays; import java.util.Objects; import org.junit.jupiter.api.AfterEach; @@ -15,10 +16,13 @@ import org.springframework.context.annotation.Import; import com.tiketeer.Tiketeer.domain.purchase.Purchase; +import com.tiketeer.Tiketeer.domain.purchase.PurchaseItem; +import com.tiketeer.Tiketeer.domain.purchase.repository.PurchaseItemRepository; import com.tiketeer.Tiketeer.domain.purchase.repository.PurchaseRepository; import com.tiketeer.Tiketeer.domain.ticket.Ticket; import com.tiketeer.Tiketeer.domain.ticket.repository.TicketRepository; import com.tiketeer.Tiketeer.domain.ticketing.Ticketing; +import com.tiketeer.Tiketeer.domain.ticketing.usecase.dto.GetTicketing; import com.tiketeer.Tiketeer.testhelper.TestHelper; @Import({TestHelper.class}) @@ -32,6 +36,8 @@ class TicketingRepositoryTest { private PurchaseRepository purchaseRepository; @Autowired private TicketingRepository ticketingRepository; + @Autowired + private PurchaseItemRepository purchaseItemRepository; @BeforeEach void initTable() { @@ -52,7 +58,6 @@ void findTicketingWithTicketStock() { var member = testHelper.createMember("user@example.com", "password"); var ticketing = ticketingRepository.save( Ticketing.builder() - .price(1000) .member(member) .description("") .title("test") @@ -65,9 +70,29 @@ void findTicketingWithTicketStock() { .build() ); var purchase = purchaseRepository.save(new Purchase(member)); - ticketRepository.save(new Ticket(null, ticketing)); - ticketRepository.save(new Ticket(purchase, ticketing)); - ticketRepository.save(new Ticket(purchase, ticketing)); + + var t1 = new Ticket(1000, 1, 1, "title 1", "description 1", ticketing); + var t2 = new Ticket(2000, 2, 2, "title 2", "description 1", ticketing); + var t3 = new Ticket(3000, 3, 3, "title 3", "description 1", ticketing); + ticketRepository.save(t1); + ticketRepository.save(t2); + ticketRepository.save(t3); + + var t1Count = 3; + purchaseItemRepository.saveAll(Arrays.stream(new int[t1Count]) + .mapToObj(i -> new PurchaseItem(purchase, t1)) + .toList()); + var t2Count = 3; + purchaseItemRepository.saveAll(Arrays.stream(new int[t2Count]) + .mapToObj(i -> new PurchaseItem(purchase, t2)) + .toList() + ); + var t3Count = 1; + purchaseItemRepository.saveAll(Arrays.stream(new int[t3Count]) + .mapToObj(i -> new PurchaseItem(purchase, t3)) + .toList() + ); + //when var memberTicketingSale = ticketingRepository.findTicketingWithTicketStock(member.getEmail()).getFirst(); @@ -79,8 +104,9 @@ void findTicketingWithTicketStock() { assertThat(memberTicketingSale.getSaleStart()).isEqualTo(now); assertThat(memberTicketingSale.getSaleEnd()).isEqualTo(now); - assertThat(memberTicketingSale.getRemainStock()).isEqualTo(1); - assertThat(memberTicketingSale.getStock()).isEqualTo(3); + assertThat(memberTicketingSale.getRemainStock()).isEqualTo(6); + assertThat(memberTicketingSale.getStock()).isEqualTo(6); + assertThat(memberTicketingSale.getPrice()).isEqualTo(12000); } @@ -92,7 +118,6 @@ void findAllTicketingsWithRemainStockSuccess() { var member = testHelper.createMember("user@example.com", "password"); var ticketing1 = ticketingRepository.save( Ticketing.builder() - .price(1000) .member(member) .description("") .title("test1") @@ -106,11 +131,11 @@ void findAllTicketingsWithRemainStockSuccess() { ); var stock1 = 3; for (int i = 0; i < stock1; i++) { - ticketRepository.save(new Ticket(null, ticketing1)); + ticketRepository.save( + new Ticket(1000 * (i + 1), i + 1, i + 1, "title " + i, "description " + i, ticketing1)); } var ticketing2 = ticketingRepository.save( Ticketing.builder() - .price(1000) .member(member) .description("") .title("test2") @@ -124,7 +149,8 @@ void findAllTicketingsWithRemainStockSuccess() { ); var stock2 = 1; for (int i = 0; i < stock2; i++) { - ticketRepository.save(new Ticket(null, ticketing2)); + ticketRepository.save( + new Ticket(1000 * (i + 1), i + 1, i + 1, "title " + i, "description " + i, ticketing2)); } //when @@ -141,16 +167,20 @@ void findAllTicketingsWithRemainStockSuccess() { .findFirst(); assertThat(ticketing1Opt).isPresent(); assertThat(ticketing2Opt).isPresent(); + assertThat(ticketing1Opt.get().getLocation()).isEqualTo("Seoul"); assertThat(ticketing1Opt.get().getRunningMinutes()).isEqualTo(600); assertThat(ticketing1Opt.get().getSaleStart()).isEqualTo(now); assertThat(ticketing1Opt.get().getSaleEnd()).isEqualTo(now); - assertThat(ticketing1Opt.get().getRemainStock()).isEqualTo(stock1); + assertThat(ticketing1Opt.get().getRemainStock()).isEqualTo((stock1 * (stock1 + 1)) / 2); + assertThat(ticketing1Opt.get().getPrice()).isEqualTo(1000); + assertThat(ticketing2Opt.get().getLocation()).isEqualTo("Seoul"); assertThat(ticketing2Opt.get().getRunningMinutes()).isEqualTo(600); assertThat(ticketing2Opt.get().getSaleStart()).isEqualTo(now); assertThat(ticketing2Opt.get().getSaleEnd()).isEqualTo(now); - assertThat(ticketing2Opt.get().getRemainStock()).isEqualTo(stock2); + assertThat(ticketing2Opt.get().getRemainStock()).isEqualTo((stock2 + (stock2 + 1)) / 2); + assertThat(ticketing2Opt.get().getPrice()).isEqualTo(1000); } @Test @@ -161,7 +191,6 @@ void findTicketingWithRemainStock() { var member = testHelper.createMember("user@example.com", "password"); var ticketing = ticketingRepository.save( Ticketing.builder() - .price(1000) .member(member) .description("") .title("test") @@ -175,12 +204,8 @@ void findTicketingWithRemainStock() { ); var purchase = purchaseRepository.save(new Purchase(member)); var stock = 3; - var remainStock = 1; - for (int i = 0; i < stock - remainStock; i++) { - ticketRepository.save(new Ticket(purchase, ticketing)); - } - for (int i = 0; i < remainStock; i++) { - ticketRepository.save(new Ticket(null, ticketing)); + for (int i = 0; i < stock; i++) { + ticketRepository.save(new Ticket(1000 * (i + 1), i + 1, i, "title " + i, "description " + i, ticketing)); } //when @@ -193,7 +218,12 @@ void findTicketingWithRemainStock() { assertThat(result.getDescription()).isEqualTo(""); assertThat(result.getSaleStart()).isEqualTo(now); assertThat(result.getSaleEnd()).isEqualTo(now); - assertThat(result.getRemainStock()).isEqualTo(remainStock); - assertThat(result.getStock()).isEqualTo(stock); + + assertThat(result.getTickets().size()).isEqualTo(stock); + GetTicketing.GetTicketingTickets ticket = result.getTickets().getFirst(); + assertThat(ticket.getTitle()).isEqualTo("title 0"); + + assertThat(ticket.getRemainingStock()).isEqualTo(0); + assertThat(ticket.getStock()).isEqualTo(1); } } \ No newline at end of file diff --git a/src/test/java/com/tiketeer/Tiketeer/domain/ticketing/service/TicketingServiceTest.java b/src/test/java/com/tiketeer/Tiketeer/domain/ticketing/service/TicketingServiceTest.java index 463a1098..14e50454 100644 --- a/src/test/java/com/tiketeer/Tiketeer/domain/ticketing/service/TicketingServiceTest.java +++ b/src/test/java/com/tiketeer/Tiketeer/domain/ticketing/service/TicketingServiceTest.java @@ -76,7 +76,6 @@ void findByIdSuccess() { var ticketing = ticketingService.saveTicketing(Ticketing.builder() .member(member) .title("tt") - .price(100L) .runningMinutes(100) .category("์นดํ…Œ๊ณ ๋ฆฌ") .location("์„œ์šธ ์–ด๋”˜๊ฐ€") @@ -105,7 +104,6 @@ void saveTicketingFailBecauseSaleStartAndEndNotValid() { var ticketingBeforeSave = Ticketing.builder() .member(member) .title("tt") - .price(100L) .runningMinutes(100) .category("์นดํ…Œ๊ณ ๋ฆฌ") .location("์„œ์šธ ์–ด๋”˜๊ฐ€") @@ -134,7 +132,6 @@ void saveTicketingFailBecauseSaleEndAndEventTimeNotValid() { var ticketingBeforeSave = Ticketing.builder() .member(member) .title("tt") - .price(100L) .runningMinutes(100) .category("์นดํ…Œ๊ณ ๋ฆฌ") .location("์„œ์šธ ์–ด๋”˜๊ฐ€") @@ -163,7 +160,6 @@ void saveTicketingSuccess() { var ticketingBeforeSave = Ticketing.builder() .member(member) .title("tt") - .price(100L) .runningMinutes(100) .category("์นดํ…Œ๊ณ ๋ฆฌ") .location("์„œ์šธ ์–ด๋”˜๊ฐ€") @@ -202,7 +198,6 @@ void deleteTicketingSuccess() { var ticketing = ticketingService.saveTicketing(Ticketing.builder() .member(member) .title("tt") - .price(100L) .runningMinutes(100) .category("์นดํ…Œ๊ณ ๋ฆฌ") .location("์„œ์šธ ์–ด๋”˜๊ฐ€") @@ -234,7 +229,6 @@ private List createTicketings(Member member, int count) { .location("์„œ์šธ") .category("์ฝ˜์„œํŠธ") .runningMinutes(100) - .price(10000) .eventTime(LocalDateTime.now().plusMonths(2)) .saleStart(LocalDateTime.now().minusMonths(1)) .saleEnd(LocalDateTime.now().plusMonths(1)) @@ -251,8 +245,7 @@ private List createTickets(Ticketing ticketing, int stock) { private Purchase createPurchase(Member member, Ticketing ticketing, int count) { var purchase = purchaseRepository.save(Purchase.builder().member(member).build()); - var tickets = ticketRepository.findByTicketingIdAndPurchaseIsNullOrderById(ticketing.getId(), Limit.of(count)); - tickets.forEach(ticket -> ticket.setPurchase(purchase)); + var tickets = ticketRepository.findByTicketingIdOrderById(ticketing.getId(), Limit.of(count)); return purchase; } } diff --git a/src/test/java/com/tiketeer/Tiketeer/domain/ticketing/service/TicketingStockServiceTest.java b/src/test/java/com/tiketeer/Tiketeer/domain/ticketing/service/TicketingStockServiceTest.java index 27774b1d..323cf016 100644 --- a/src/test/java/com/tiketeer/Tiketeer/domain/ticketing/service/TicketingStockServiceTest.java +++ b/src/test/java/com/tiketeer/Tiketeer/domain/ticketing/service/TicketingStockServiceTest.java @@ -1,6 +1,8 @@ package com.tiketeer.Tiketeer.domain.ticketing.service; import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.List; import java.util.UUID; import org.assertj.core.api.Assertions; @@ -15,6 +17,7 @@ import com.tiketeer.Tiketeer.domain.member.Member; import com.tiketeer.Tiketeer.domain.ticket.repository.TicketRepository; +import com.tiketeer.Tiketeer.domain.ticket.usecase.dto.CreateTicketMetadata; import com.tiketeer.Tiketeer.domain.ticketing.Ticketing; import com.tiketeer.Tiketeer.domain.ticketing.exception.TicketingNotFoundException; import com.tiketeer.Tiketeer.domain.ticketing.repository.TicketingRepository; @@ -52,7 +55,10 @@ void createStockFailBecauseNotExistTicketing() { Assertions.assertThatThrownBy(() -> { // when - ticketingStockService.createStock(invalidTicketingId, 100); + ticketingStockService.createStock( + invalidTicketingId, + List.of(new CreateTicketMetadata(10, 10, "Gold", "", 1000) + )); // then }).isInstanceOf(TicketingNotFoundException.class); } @@ -71,11 +77,14 @@ void createStockSuccess() { Assertions.assertThat(ticketRepository.findAllByTicketing(ticketing)).isEmpty(); // when - ticketingStockService.createStock(ticketing.getId(), addStock); + ticketingStockService.createStock( + ticketing.getId(), + List.of(new CreateTicketMetadata(addStock, addStock, "Gold", "", 1000)) + ); // then var tickets = ticketRepository.findAllByTicketing(ticketing); - Assertions.assertThat(tickets.size()).isEqualTo(addStock); + Assertions.assertThat(tickets.getFirst().getStock()).isEqualTo(addStock); } @Test @@ -86,7 +95,8 @@ void updateStockFailBecauseNotExistTicketing() { Assertions.assertThatThrownBy(() -> { // when - ticketingStockService.updateStock(invalidTicketingId, 10); + ticketingStockService.updateStock(invalidTicketingId, + List.of(new CreateTicketMetadata(1, 1, "Gold", "", 1000))); // then }).isInstanceOf(TicketingNotFoundException.class); } @@ -101,21 +111,24 @@ void updateStockSuccessForAddRequest() { var ticketing = createTicketing(member); var initStock = 10; - ticketingStockService.createStock(ticketing.getId(), initStock); + ticketingStockService.createStock(ticketing.getId(), + List.of(new CreateTicketMetadata(initStock, initStock, "Gold", "", 1000))); - Assertions.assertThat(ticketRepository.findAllByTicketing(ticketing).size()).isEqualTo(initStock); + Assertions.assertThat(ticketRepository.findAllByTicketing(ticketing).getFirst().getStock()) + .isEqualTo(initStock); // when var updateStock = 20; - ticketingStockService.updateStock(ticketing.getId(), updateStock); + ticketingStockService.updateStock(ticketing.getId(), + List.of(new CreateTicketMetadata(updateStock, updateStock, "Gold", "", 1000))); // then var tickets = ticketRepository.findAllByTicketing(ticketing); - Assertions.assertThat(tickets.size()).isEqualTo(updateStock); + Assertions.assertThat(tickets.getFirst().getStock()).isEqualTo(updateStock); } @Test - @DisplayName("๊ธฐ์กด ์žฌ๊ณ ์˜ ์ˆ˜(30) > ์žฌ๊ณ  ์ˆ˜์ • ์š”์ฒญ(10) > ์žฌ๊ณ  ์—…๋ฐ์ดํŠธ ์„ฑ๊ณต (30 -> 10)") + @DisplayName("๊ธฐ์กด ํ‹ฐ์ผ“์˜ ์ˆ˜(1) > ํ‹ฐ์ผ“ ์ถ”๊ฐ€ ์—…๋ฐ์ดํŠธ > ํ‹ฐ์ผ“ ์—…๋ฐ์ดํŠธ ์„ฑ๊ณต (1 -> 2)") @Transactional void updateStockSuccessForRemoveRequest() { // given @@ -124,17 +137,21 @@ void updateStockSuccessForRemoveRequest() { var ticketing = createTicketing(member); var initStock = 30; - ticketingStockService.createStock(ticketing.getId(), initStock); + ticketingStockService.createStock(ticketing.getId(), + List.of(new CreateTicketMetadata(initStock, initStock, "Gold", "", 1000))); - Assertions.assertThat(ticketRepository.findAllByTicketing(ticketing).size()).isEqualTo(initStock); + Assertions.assertThat(ticketRepository.findAllByTicketing(ticketing).size()).isEqualTo(1); // when var updateStock = 10; - ticketingStockService.updateStock(ticketing.getId(), updateStock); + var ticketsMeta = Arrays.asList( + new CreateTicketMetadata(initStock, initStock, "Gold", "", 2000), + new CreateTicketMetadata(initStock, initStock, "Silver", "", 1000)); + ticketingStockService.updateStock(ticketing.getId(), ticketsMeta); // then var tickets = ticketRepository.findAllByTicketing(ticketing); - Assertions.assertThat(tickets.size()).isEqualTo(updateStock); + Assertions.assertThat(tickets.size()).isEqualTo(2); } @Test @@ -160,9 +177,13 @@ void dropAllStockSuccess() { var ticketing = createTicketing(member); var initStock = 30; - ticketingStockService.createStock(ticketing.getId(), initStock); + var ticketsMeta = Arrays.asList( + new CreateTicketMetadata(initStock, initStock, "Gold", "", 1000), + new CreateTicketMetadata(initStock, initStock, "Silver", "", 1000) + ); + ticketingStockService.createStock(ticketing.getId(), ticketsMeta); - Assertions.assertThat(ticketRepository.findAllByTicketing(ticketing).size()).isEqualTo(initStock); + Assertions.assertThat(ticketRepository.findAllByTicketing(ticketing).size()).isEqualTo(2); // when ticketingStockService.dropAllStock(ticketing.getId()); @@ -180,7 +201,6 @@ private Ticketing createTicketing(Member member) { .location("์„œ์šธ ๊ฐ•๋‚จ์—ญ 8๋ฒˆ ์ถœ๊ตฌ") .category("์Œ์•…ํšŒ") .runningMinutes(100) - .price(10000L) .saleStart(now.plusYears(1)) .saleEnd(now.plusYears(2)) .eventTime(now.plusYears(3)).build()); diff --git a/src/test/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/CreateTicketingUseCaseTest.java b/src/test/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/CreateTicketingUseCaseTest.java index 3b294b93..7e93914a 100644 --- a/src/test/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/CreateTicketingUseCaseTest.java +++ b/src/test/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/CreateTicketingUseCaseTest.java @@ -4,6 +4,9 @@ import java.nio.file.Path; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -15,14 +18,18 @@ import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.web.multipart.MultipartFile; import com.tiketeer.Tiketeer.constant.StorageEnum; import com.tiketeer.Tiketeer.domain.member.exception.MemberNotFoundException; import com.tiketeer.Tiketeer.domain.ticket.repository.TicketRepository; +import com.tiketeer.Tiketeer.domain.ticket.usecase.dto.CreateTicketMetadata; import com.tiketeer.Tiketeer.domain.ticketing.exception.EventTimeNotValidException; import com.tiketeer.Tiketeer.domain.ticketing.exception.SaleDurationNotValidException; import com.tiketeer.Tiketeer.domain.ticketing.repository.TicketingRepository; import com.tiketeer.Tiketeer.domain.ticketing.usecase.dto.CreateTicketingCommandDto; +import com.tiketeer.Tiketeer.infra.storage.exception.UploadFileRuntimeException; import com.tiketeer.Tiketeer.infra.storage.strategy.FileStorageStrategy; import com.tiketeer.Tiketeer.infra.storage.strategy.LocalFileStorageStrategy; import com.tiketeer.Tiketeer.testhelper.TestHelper; @@ -160,9 +167,6 @@ void createTicketingSuccess() { assertThat(ticketing.getSaleStart()).isEqualToIgnoringNanos(command.getSaleStart()); assertThat(ticketing.getSaleEnd()).isEqualToIgnoringNanos(command.getSaleEnd()); assertThat(ticketing.getStorageEnum()).isEqualTo(StorageEnum.LOCAL); - - var tickets = ticketRepository.findAllByTicketing(ticketing); - assertThat(tickets.size()).isEqualTo(command.getStock()); } private CreateTicketingCommandDto createTicketingCommand(String email, LocalDateTime eventTime, @@ -173,8 +177,6 @@ private CreateTicketingCommandDto createTicketingCommand(String email, LocalDate .location("์„œ์šธ ๊ฐ•๋‚จ์—ญ 8๋ฒˆ ์ถœ๊ตฌ") .category("์Œ์•…ํšŒ") .runningMinutes(100) - .price(10000L) - .stock(20) .eventTime(eventTime) .saleStart(saleStart) .saleEnd(saleEnd) diff --git a/src/test/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/DeleteTicketingUseCaseTest.java b/src/test/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/DeleteTicketingUseCaseTest.java index 5daa6b14..8d3de22b 100644 --- a/src/test/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/DeleteTicketingUseCaseTest.java +++ b/src/test/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/DeleteTicketingUseCaseTest.java @@ -130,8 +130,6 @@ void deleteTicketingSuccess() { Assertions.assertThat(ticketingOpt.isPresent()).isTrue(); var ticketing = ticketingOpt.get(); - Assertions.assertThat(ticketRepository.findAllByTicketing(ticketing).size()) - .isEqualTo(createTicketingCommand.getStock()); var deleteTicketingCommand = DeleteTicketingCommandDto.builder() .ticketingId(ticketingId) @@ -159,8 +157,6 @@ private CreateTicketingCommandDto createTicketingCommand(String email, LocalDate .location("์„œ์šธ ๊ฐ•๋‚จ์—ญ 8๋ฒˆ ์ถœ๊ตฌ") .category("์Œ์•…ํšŒ") .runningMinutes(100) - .price(10000L) - .stock(20) .eventTime(eventTime) .saleStart(saleStart) .saleEnd(saleEnd) diff --git a/src/test/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/GetAllTicketingsUseCaseTest.java b/src/test/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/GetAllTicketingsUseCaseTest.java index 400b62bc..0d5fae11 100644 --- a/src/test/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/GetAllTicketingsUseCaseTest.java +++ b/src/test/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/GetAllTicketingsUseCaseTest.java @@ -85,14 +85,13 @@ private List createTicketings(Member member, int count) { .location("์„œ์šธ") .category("์ฝ˜์„œํŠธ") .runningMinutes(100) - .price(10000) .eventTime(LocalDateTime.now().plusMonths(2)) .saleStart(LocalDateTime.now().minusMonths(1)) .saleEnd(LocalDateTime.now().plusMonths(1)) .build() ).toList(); ticketings.forEach(ticketing -> { - ticketRepository.save(new Ticket(null, ticketing)); + ticketRepository.save(new Ticket(1000, 1, 1, "title 1", "description 1", ticketing)); }); return ticketingRepository.saveAll(ticketings); } diff --git a/src/test/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/GetTicketingUseCaseTest.java b/src/test/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/GetTicketingUseCaseTest.java index f9325ce9..54689975 100644 --- a/src/test/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/GetTicketingUseCaseTest.java +++ b/src/test/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/GetTicketingUseCaseTest.java @@ -62,10 +62,7 @@ void getTicketingSuccess() { var member = testHelper.createMember(mockEmail); var ticketings = createTicketings(member, 1); var ticketing = ticketings.getFirst(); - var stock = 10; - var purchasedStock = 2; - createTickets(ticketing, stock); - createPurchase(member, ticketing, purchasedStock); + createTickets(ticketing); var command = GetTicketingCommandDto.builder().ticketingId(ticketing.getId()).build(); @@ -74,8 +71,8 @@ void getTicketingSuccess() { // then Assertions.assertThat(result.getTitle()).isEqualTo("0"); - Assertions.assertThat(result.getStock()).isEqualTo(stock); - Assertions.assertThat(result.getRemainStock()).isEqualTo(stock - purchasedStock); + Assertions.assertThat(result.getTickets().size()).isEqualTo(3); + Assertions.assertThat(result.getTickets().getFirst().getStock()).isEqualTo(10); Assertions.assertThat(result.getOwner()).isEqualTo(member.getEmail()); } @@ -92,7 +89,6 @@ private List createTicketings(Member member, int count) { .location("์„œ์šธ") .category("์ฝ˜์„œํŠธ") .runningMinutes(100) - .price(10000) .eventTime(LocalDateTime.now().plusMonths(2)) .saleStart(LocalDateTime.now().minusMonths(1)) .saleEnd(LocalDateTime.now().plusMonths(1)) @@ -101,16 +97,14 @@ private List createTicketings(Member member, int count) { return ticketingRepository.saveAll(ticketings); } - private List createTickets(Ticketing ticketing, int stock) { - return ticketRepository.saveAll(Arrays.stream(new int[stock]) - .mapToObj(i -> Ticket.builder().ticketing(ticketing).build()) - .toList()); + private List createTickets(Ticketing ticketing) { + return ticketRepository.saveAll( + Arrays.asList( + new Ticket(3000, 10, 10, "Gold", "", ticketing), + new Ticket(2000, 10, 10, "Silver", "", ticketing), + new Ticket(3000, 10, 10, "Bronze", "", ticketing) + ) + ); } - private Purchase createPurchase(Member member, Ticketing ticketing, int count) { - var purchase = purchaseRepository.save(Purchase.builder().member(member).build()); - var tickets = ticketRepository.findByTicketingIdAndPurchaseIsNullOrderById(ticketing.getId(), Limit.of(count)); - tickets.forEach(ticket -> ticket.setPurchase(purchase)); - return purchase; - } } \ No newline at end of file diff --git a/src/test/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/UpdateTicketingUseCaseTest.java b/src/test/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/UpdateTicketingUseCaseTest.java index 93095346..561a1835 100644 --- a/src/test/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/UpdateTicketingUseCaseTest.java +++ b/src/test/java/com/tiketeer/Tiketeer/domain/ticketing/usecase/UpdateTicketingUseCaseTest.java @@ -104,11 +104,9 @@ void updateTicketingFailBecauseNotOwnedTicketing() { .ticketingId(ticketingId) .email(memberEmailNotOwnedTicketing) .title(createCmd.getTitle()) - .price(createCmd.getPrice()) .description(createCmd.getDescription()) .category(createCmd.getCategory()) .runningMinutes(createCmd.getRunningMinutes()) - .stock(createCmd.getStock()) .saleStart(createCmd.getSaleStart()) .saleEnd(createCmd.getSaleEnd()) .eventTime(createCmd.getEventTime()) @@ -141,11 +139,9 @@ void updateTicketingFailBecauseSaleDurationHasBeenStarted() { .ticketingId(ticketingId) .email(createCmd.getMemberEmail()) .title(createCmd.getTitle()) - .price(createCmd.getPrice()) .description(createCmd.getDescription()) .category(createCmd.getCategory()) .runningMinutes(createCmd.getRunningMinutes()) - .stock(createCmd.getStock()) .saleStart(createCmd.getSaleStart()) .saleEnd(createCmd.getSaleEnd()) .eventTime(createCmd.getEventTime()) @@ -178,11 +174,9 @@ void updateTicketingFailBecauseInvalidEventTime() { .ticketingId(ticketingId) .email(createCmd.getMemberEmail()) .title(createCmd.getTitle()) - .price(createCmd.getPrice()) .description(createCmd.getDescription()) .category(createCmd.getCategory()) .runningMinutes(createCmd.getRunningMinutes()) - .stock(createCmd.getStock()) .saleStart(createCmd.getSaleStart()) .saleEnd(createCmd.getSaleEnd()) .eventTime(now.minusDays(1)) @@ -214,11 +208,9 @@ void updateTicketingFailBecauseSaleDurationNotValid() { .ticketingId(ticketingId) .email(createCmd.getMemberEmail()) .title(createCmd.getTitle()) - .price(createCmd.getPrice()) .description(createCmd.getDescription()) .category(createCmd.getCategory()) .runningMinutes(createCmd.getRunningMinutes()) - .stock(createCmd.getStock()) .saleStart(saleEnd) .saleEnd(saleStart) .eventTime(eventTime) @@ -250,11 +242,9 @@ void updateTicketingFailBecauseEventTimeBeforeSaleEnd() { .ticketingId(ticketingId) .email(createCmd.getMemberEmail()) .title(createCmd.getTitle()) - .price(createCmd.getPrice()) .description(createCmd.getDescription()) .category(createCmd.getCategory()) .runningMinutes(createCmd.getRunningMinutes()) - .stock(createCmd.getStock()) .saleStart(saleStart) .saleEnd(saleEnd) .eventTime(saleEnd.minusDays(1)) @@ -284,11 +274,9 @@ void updateTicketingSuccess() { var newTitle = "New Title!"; var newDescription = "New!!!"; - var newPrice = createCmd.getPrice() * 2; var newCategory = createCmd.getCategory() + "!"; var newLocation = createCmd.getLocation() + "!"; var newRunningMinutes = createCmd.getRunningMinutes() * 2; - var newStock = createCmd.getStock() + 10; var newSaleStart = createCmd.getSaleStart().plusMonths(1); var newSaleEnd = createCmd.getSaleEnd().plusMonths(1); var newEventTime = createCmd.getEventTime().plusMonths(1); @@ -297,12 +285,10 @@ void updateTicketingSuccess() { .ticketingId(ticketingId) .email(createCmd.getMemberEmail()) .title(newTitle) - .price(newPrice) .location(newLocation) .description(newDescription) .category(newCategory) .runningMinutes(newRunningMinutes) - .stock(newStock) .saleStart(newSaleStart) .saleEnd(newSaleEnd) .eventTime(newEventTime) @@ -321,13 +307,9 @@ void updateTicketingSuccess() { Assertions.assertThat(updatedTicketing.getCategory()).isEqualTo(newCategory); Assertions.assertThat(updatedTicketing.getLocation()).isEqualTo(newLocation); Assertions.assertThat(updatedTicketing.getRunningMinutes()).isEqualTo(newRunningMinutes); - Assertions.assertThat(updatedTicketing.getPrice()).isEqualTo(newPrice); Assertions.assertThat(updatedTicketing.getEventTime()).isEqualToIgnoringNanos(newEventTime); Assertions.assertThat(updatedTicketing.getSaleStart()).isEqualToIgnoringNanos(newSaleStart); Assertions.assertThat(updatedTicketing.getSaleEnd()).isEqualToIgnoringNanos(newSaleEnd); - - var tickets = ticketRepository.findAllByTicketing(updatedTicketing); - Assertions.assertThat(tickets.size()).isEqualTo(newStock); } private CreateTicketingCommandDto createTicketingCommand(String email, LocalDateTime eventTime, @@ -338,8 +320,6 @@ private CreateTicketingCommandDto createTicketingCommand(String email, LocalDate .location("์„œ์šธ ๊ฐ•๋‚จ์—ญ 8๋ฒˆ ์ถœ๊ตฌ") .category("์Œ์•…ํšŒ") .runningMinutes(100) - .price(10000L) - .stock(20) .eventTime(eventTime) .saleStart(saleStart) .saleEnd(saleEnd) diff --git a/src/test/java/com/tiketeer/Tiketeer/infra/redis/RedisCacheTest.java b/src/test/java/com/tiketeer/Tiketeer/infra/redis/RedisCacheTest.java index 7b278266..57ac7401 100644 --- a/src/test/java/com/tiketeer/Tiketeer/infra/redis/RedisCacheTest.java +++ b/src/test/java/com/tiketeer/Tiketeer/infra/redis/RedisCacheTest.java @@ -3,6 +3,7 @@ import static org.assertj.core.api.Assertions.*; import java.time.LocalDateTime; + import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -76,7 +77,6 @@ void cacheUpdateTest() throws JsonProcessingException { CreateTicketingResultDto ticketing = createTicketing(email, saleStart, saleEnd); - LocalDateTime changedSaleStart = LocalDateTime.now().plusDays(10); LocalDateTime changedSaleEnd = LocalDateTime.now().plusDays(20); @@ -86,15 +86,15 @@ void cacheUpdateTest() throws JsonProcessingException { .ticketingId(ticketing.getTicketingId()) .build(); - redisTemplate.opsForValue().set("ticketingId::" + ticketing.getTicketingId().toString(), objectMapper.writeValueAsString(updateTicketingSalePeriodDto)); + redisTemplate.opsForValue() + .set("ticketingId::" + ticketing.getTicketingId().toString(), + objectMapper.writeValueAsString(updateTicketingSalePeriodDto)); // when UpdateTicketingCommandDto updateTicketingCommandDto = UpdateTicketingCommandDto.builder() .email(email) - .price(1000L) .category("") .runningMinutes(100) - .stock(1) .saleStart(changedSaleStart) .saleEnd(changedSaleEnd) .location("") @@ -129,7 +129,9 @@ void cacheDeleteTest() throws JsonProcessingException { .ticketingId(ticketing.getTicketingId()) .build(); - redisTemplate.opsForValue().set("ticketingId::" + ticketing.getTicketingId().toString(), objectMapper.writeValueAsString(updateTicketingSalePeriodDto)); + redisTemplate.opsForValue() + .set("ticketingId::" + ticketing.getTicketingId().toString(), + objectMapper.writeValueAsString(updateTicketingSalePeriodDto)); // when deleteTicketingUseCase.deleteTicketing(DeleteTicketingCommandDto.builder() @@ -144,9 +146,7 @@ void cacheDeleteTest() throws JsonProcessingException { private CreateTicketingResultDto createTicketing(String email, LocalDateTime saleStart, LocalDateTime saleEnd) { CreateTicketingCommandDto createTicketingCommandDto = CreateTicketingCommandDto.builder() - .price(1000L) .runningMinutes(100) - .stock(1) .memberEmail(email) .saleEnd(saleEnd) .saleStart(saleStart) diff --git a/src/test/java/com/tiketeer/Tiketeer/testhelper/TestHelperTest.java b/src/test/java/com/tiketeer/Tiketeer/testhelper/TestHelperTest.java index 00f7736a..1a1904e3 100644 --- a/src/test/java/com/tiketeer/Tiketeer/testhelper/TestHelperTest.java +++ b/src/test/java/com/tiketeer/Tiketeer/testhelper/TestHelperTest.java @@ -111,7 +111,6 @@ void cleanDB() { otpRepository.save(Otp.builder().member(mockMember).expiredAt(LocalDateTime.of(9999, 12, 31, 0, 0)).build()); var mockTicketing = ticketingRepository.save(Ticketing.builder() - .price(10000) .member(mockMember) .title("Mock Ticketing") .location("์„œ์šธ ์–ด๋”˜๊ฐ€") @@ -124,7 +123,7 @@ void cleanDB() { var mockPurchase = purchaseRepository.save(Purchase.builder().member(mockMember).build()); - ticketRepository.save(Ticket.builder().ticketing(mockTicketing).purchase(mockPurchase).build()); + ticketRepository.save(Ticket.builder().ticketing(mockTicketing).title("test").stock(1).remainingStock(1).description("").build()); var repoForTestList = List.of( ticketingRepository,