diff --git a/src/main/java/umc/th/juinjang/api/note/shared/service/SharedNoteCommandService.java b/src/main/java/umc/th/juinjang/api/note/shared/service/SharedNoteCommandService.java index 4e88d35b..f5050baa 100644 --- a/src/main/java/umc/th/juinjang/api/note/shared/service/SharedNoteCommandService.java +++ b/src/main/java/umc/th/juinjang/api/note/shared/service/SharedNoteCommandService.java @@ -111,7 +111,7 @@ private void consumePurchasedPencils(Member buyer, long unpaidPencil) { long available = purchasedPencil.getRemainQuantity(); long toConsume = Math.min(available, remainingToConsume); - purchasedPencil.decreaseRemainQuantity(toConsume); // remainQuantity -= toConsume + purchasedPencil.decreaseUsedQuantity(toConsume); // remainQuantity -= toConsume remainingToConsume -= toConsume; } diff --git a/src/main/java/umc/th/juinjang/api/pencil/service/PencilCommandService.java b/src/main/java/umc/th/juinjang/api/pencil/service/PencilCommandService.java index 4d66b4bc..61d255a2 100644 --- a/src/main/java/umc/th/juinjang/api/pencil/service/PencilCommandService.java +++ b/src/main/java/umc/th/juinjang/api/pencil/service/PencilCommandService.java @@ -107,10 +107,13 @@ public void handleSuccessfulApplePurchase(AppleIAPPurchaseRequest request, Membe Long pencilAmount = request.getPencilQuantity(); String title = createTitle(pencilAmount); - purchasedPencilUpdater.save(PurchasedPencil.successOf(member, title, pencilAmount, request.getPrice(), - request.getPlayTime(), transactionId, request.getAppAccountToken(), now)); + PencilAccount buyer = pencilAccountFinder.findByMemberWithLock(member); + buyer.increasePurchasedBalance(pencilAmount); + + purchasedPencilUpdater.save( + PurchasedPencil.successOf(member, title, pencilAmount, buyer.getTotalBalance(), request.getPrice(), + request.getPlayTime(), transactionId, request.getAppAccountToken(), now)); - pencilAccountFinder.findByMemberWithLock(member).increasePurchasedBalance(pencilAmount); } @Transactional diff --git a/src/main/java/umc/th/juinjang/api/pencil/service/response/AcquiredPencilResponse.java b/src/main/java/umc/th/juinjang/api/pencil/service/response/AcquiredPencilResponse.java index 0b679432..460fb895 100644 --- a/src/main/java/umc/th/juinjang/api/pencil/service/response/AcquiredPencilResponse.java +++ b/src/main/java/umc/th/juinjang/api/pencil/service/response/AcquiredPencilResponse.java @@ -9,13 +9,13 @@ @Getter public class AcquiredPencilResponse { - private Long acquiredPencilId; - private String content; - private Long sharedNoteId; - private Long acquiredQuantity; - private boolean isRead; - private String type; - private LocalDateTime createdAt; + private final Long acquiredPencilId; + private final String content; + private final Long sharedNoteId; + private final Long acquiredQuantity; + private final boolean isRead; + private final String type; + private final LocalDateTime createdAt; @Builder public AcquiredPencilResponse(Long acquiredPencilId, String content, Long sharedNoteId, Long acquiredQuantity, @@ -31,7 +31,7 @@ public AcquiredPencilResponse(Long acquiredPencilId, String content, Long shared public static AcquiredPencilResponse from(AcquiredPencil acquiredPencil) { return AcquiredPencilResponse.builder() - .acquiredPencilId(acquiredPencil.getAcquiredPencilId()) + .acquiredPencilId(acquiredPencil.getId()) .content(acquiredPencil.getContent()) .sharedNoteId(acquiredPencil.getSharedNoteId()) .acquiredQuantity(acquiredPencil.getAcquiredQuantity()) diff --git a/src/main/java/umc/th/juinjang/api/pencil/service/response/PurchasedPencilResponse.java b/src/main/java/umc/th/juinjang/api/pencil/service/response/PurchasedPencilResponse.java index a9ff357d..6343a248 100644 --- a/src/main/java/umc/th/juinjang/api/pencil/service/response/PurchasedPencilResponse.java +++ b/src/main/java/umc/th/juinjang/api/pencil/service/response/PurchasedPencilResponse.java @@ -8,12 +8,12 @@ @Getter public class PurchasedPencilResponse { - private Long purchasePencilId; - private Long purchaseQuantity; - private Long remainQuantity; - private String title; - private Long price; - private LocalDateTime purchasedAt; + private final Long purchasePencilId; + private final Long purchaseQuantity; + private final Long remainQuantity; + private final String title; + private final Long price; + private final LocalDateTime purchasedAt; @Builder public PurchasedPencilResponse(Long purchasePencilId, Long purchaseQuantity, Long remainQuantity, @@ -28,7 +28,7 @@ public PurchasedPencilResponse(Long purchasePencilId, Long purchaseQuantity, Lon public static PurchasedPencilResponse from(PurchasedPencil purchasedPencil) { return PurchasedPencilResponse.builder() - .purchasePencilId(purchasedPencil.getPurchasedPencilId()) + .purchasePencilId(purchasedPencil.getId()) .purchaseQuantity(purchasedPencil.getPurchaseQuantity()) .remainQuantity(purchasedPencil.getRemainQuantity()) .title(purchasedPencil.getTitle()) diff --git a/src/main/java/umc/th/juinjang/api/reward/service/RewardService.java b/src/main/java/umc/th/juinjang/api/reward/service/RewardService.java index 300fce8c..ae20e173 100644 --- a/src/main/java/umc/th/juinjang/api/reward/service/RewardService.java +++ b/src/main/java/umc/th/juinjang/api/reward/service/RewardService.java @@ -4,10 +4,10 @@ import org.springframework.transaction.annotation.Transactional; import lombok.RequiredArgsConstructor; -import umc.th.juinjang.domain.note.shared.model.ViewCountPolicy; import umc.th.juinjang.api.pencil.service.AcquiredPencilUpdater; import umc.th.juinjang.api.pencilAccount.service.PencilAccountFinder; import umc.th.juinjang.domain.member.model.Member; +import umc.th.juinjang.domain.note.shared.model.ViewCountPolicy; import umc.th.juinjang.domain.pencil.acquired.model.AcquiredPencil; import umc.th.juinjang.domain.pencil.acquired.model.AcquiredType; import umc.th.juinjang.domain.pencilaccount.model.PencilAccount; diff --git a/src/main/java/umc/th/juinjang/domain/pencil/acquired/model/AcquiredPencil.java b/src/main/java/umc/th/juinjang/domain/pencil/acquired/model/AcquiredPencil.java index 632a217b..f65ef692 100644 --- a/src/main/java/umc/th/juinjang/domain/pencil/acquired/model/AcquiredPencil.java +++ b/src/main/java/umc/th/juinjang/domain/pencil/acquired/model/AcquiredPencil.java @@ -2,6 +2,7 @@ import java.time.LocalDateTime; +import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; @@ -24,7 +25,8 @@ public class AcquiredPencil extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long acquiredPencilId; + @Column(name = "acquired_pencil_id") + private Long id; @ManyToOne @JoinColumn(name = "member_id", nullable = false) @@ -42,8 +44,8 @@ public class AcquiredPencil extends BaseEntity { private AcquiredType type; // Note, Add, Sold @Builder - private AcquiredPencil(Member member, String content, Long sharedNoteId, Long acquiredQuantity, boolean isRead, - AcquiredType type, LocalDateTime createdAt) { + private AcquiredPencil(Member member, String content, Long sharedNoteId, Long acquiredQuantity, Long remainQuantity, + boolean isRead, AcquiredType type, LocalDateTime createdAt) { this.member = member; this.content = content; this.sharedNoteId = sharedNoteId; diff --git a/src/main/java/umc/th/juinjang/domain/pencil/purchased/model/PurchasedPencil.java b/src/main/java/umc/th/juinjang/domain/pencil/purchased/model/PurchasedPencil.java index d3fee350..276a0023 100644 --- a/src/main/java/umc/th/juinjang/domain/pencil/purchased/model/PurchasedPencil.java +++ b/src/main/java/umc/th/juinjang/domain/pencil/purchased/model/PurchasedPencil.java @@ -31,7 +31,8 @@ public class PurchasedPencil { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long purchasedPencilId; + @Column(name = "purchased_pencil_id") + private Long id; @ManyToOne @JoinColumn(name = "member_id", nullable = false) @@ -39,7 +40,10 @@ public class PurchasedPencil { private String title; private Long purchaseQuantity; + @Comment("구매 후 사용자에게 남은 연필의 개수") private Long remainQuantity; + @Comment("구매 후 사용하고 남은 연필의 개수") + private Long usedQuantity; private Long price; @Comment("애플 인앱 결제에서, 프론트에서 전달해주는 트랜잭션 아이디") @@ -48,9 +52,11 @@ public class PurchasedPencil { @Comment("애플 인앱 결제에서, 프론트에서 전달해주는 애플 앱 토큰") private UUID appAccountToken; + @Comment("해당 결제한 연필이 정상적으로 고객에게 전달됐는 지 여부") @Convert(converter = DeliveryStatusConverter.class) private DeliveryStatus deliveryStatus; + @Comment("트랜잭션이 정상적으로 진행됐는 가 여부") @Enumerated(EnumType.STRING) @Column(nullable = false) private TransactionStatus transactionStatus; @@ -65,7 +71,7 @@ public class PurchasedPencil { private LocalDateTime updatedAt; @Builder - public PurchasedPencil(Member member, String title, Long purchaseQuantity, + public PurchasedPencil(Member member, String title, Long purchaseQuantity, Long usedQuantity, Long remainQuantity, TransactionStatus transactionStatus, Integer playTime, Long price, String transactionId, UUID appAccountToken, DeliveryStatus deliveryStatus, LocalDateTime purchasedAt) { @@ -73,6 +79,7 @@ public PurchasedPencil(Member member, String title, Long purchaseQuantity, this.title = title; this.purchaseQuantity = purchaseQuantity; this.remainQuantity = remainQuantity; + this.usedQuantity = usedQuantity; this.price = price; this.playTime = playTime; this.transactionId = transactionId; @@ -82,23 +89,6 @@ public PurchasedPencil(Member member, String title, Long purchaseQuantity, this.purchasedAt = purchasedAt; } - public void decreaseRemainQuantity(long quantity) { - this.remainQuantity -= quantity; - } - - public void markAsSuccess(){ - this.transactionStatus = TransactionStatus.SUCCESS; - this.deliveryStatus = DeliveryStatus.DELIVERY_SUCCESS; - } - - public void markAsRefund(){ - this.transactionStatus = TransactionStatus.REFUNDED; - } - - public void updateRetryCount(Long retryCount) { - this.retryCount = retryCount; - } - private static PurchasedPencilBuilder baseBuilder( Member member, String title, Long quantity, Long price, Integer playTime, String transactionId, UUID token, LocalDateTime purchasedAt @@ -107,7 +97,7 @@ private static PurchasedPencilBuilder baseBuilder( .member(member) .title(title) .purchaseQuantity(quantity) - .remainQuantity(quantity) + .usedQuantity(quantity) .price(price) .playTime(playTime) .transactionId(transactionId) @@ -116,12 +106,13 @@ private static PurchasedPencilBuilder baseBuilder( } // ✅ 결제 성공 - public static PurchasedPencil successOf(Member member, String title, Long quantity, + public static PurchasedPencil successOf(Member member, String title, Long quantity, Long remainQuantity, Long price, Integer playTime, String transactionId, UUID token, LocalDateTime purchasedAt) { return baseBuilder(member, title, quantity, price, playTime, transactionId, token, purchasedAt) .transactionStatus(TransactionStatus.SUCCESS) .deliveryStatus(DeliveryStatus.DELIVERY_SUCCESS) + .remainQuantity(remainQuantity) .build(); } @@ -131,6 +122,7 @@ public static PurchasedPencil failedDueToServerError(Member member, String title return baseBuilder(member, title, quantity, price, playTime, transactionId, token, purchasedAt) .transactionStatus(TransactionStatus.DB_FAILED) .deliveryStatus(DeliveryStatus.SERVER_ERROR) + .remainQuantity(0L) .build(); } @@ -140,8 +132,26 @@ public static PurchasedPencil failedDueToValidation(Member member, String title, return baseBuilder(member, title, quantity, price, playTime, transactionId, token, purchasedAt) .transactionStatus(TransactionStatus.VALIDATION_FAILED) .deliveryStatus(DeliveryStatus.OTHER_REASONS) + .remainQuantity(0L) .build(); } + + public void decreaseUsedQuantity(long quantity) { + this.usedQuantity -= quantity; + } + + public void markAsSuccess() { + this.transactionStatus = TransactionStatus.SUCCESS; + this.deliveryStatus = DeliveryStatus.DELIVERY_SUCCESS; + } + + public void markAsRefund() { + this.transactionStatus = TransactionStatus.REFUNDED; + } + + public void updateRetryCount(Long retryCount) { + this.retryCount = retryCount; + } } diff --git a/src/test/java/umc/th/juinjang/api/pencil/service/PencilQueryServiceTest.java b/src/test/java/umc/th/juinjang/api/pencil/service/PencilQueryServiceTest.java index 8f839e21..a5f6c76b 100644 --- a/src/test/java/umc/th/juinjang/api/pencil/service/PencilQueryServiceTest.java +++ b/src/test/java/umc/th/juinjang/api/pencil/service/PencilQueryServiceTest.java @@ -1,300 +1,312 @@ -package umc.th.juinjang.api.pencil.service; - -import static org.assertj.core.api.Assertions.*; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.UUID; - -import org.assertj.core.groups.Tuple; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; - -import com.apple.itunes.storekit.model.ConsumptionRequest; -import com.apple.itunes.storekit.model.ConsumptionStatus; -import com.apple.itunes.storekit.model.LifetimeDollarsPurchased; -import com.apple.itunes.storekit.model.LifetimeDollarsRefunded; -import com.apple.itunes.storekit.model.Platform; -import com.apple.itunes.storekit.model.PlayTime; - -import lombok.extern.slf4j.Slf4j; -import umc.th.juinjang.api.IntegrationTestSupport; -import umc.th.juinjang.api.pencil.service.response.AcquiredPencilResponse; -import umc.th.juinjang.api.pencil.service.response.PurchasedPencilResponse; -import umc.th.juinjang.api.pencil.service.response.UsedPencilResponse; -import umc.th.juinjang.domain.member.model.Member; -import umc.th.juinjang.domain.member.repository.MemberRepository; -import umc.th.juinjang.domain.pencil.acquired.model.AcquiredPencil; -import umc.th.juinjang.domain.pencil.acquired.model.AcquiredType; -import umc.th.juinjang.domain.pencil.acquired.repository.AcquiredPencilRepository; -import umc.th.juinjang.domain.pencil.purchased.model.PurchasedPencil; -import umc.th.juinjang.domain.pencil.purchased.repository.PurchasedPencilRepository; -import umc.th.juinjang.domain.pencil.used.model.UsedPencil; -import umc.th.juinjang.domain.pencil.used.model.Usedtype; -import umc.th.juinjang.domain.pencil.used.repository.UsedPencilRepository; -import umc.th.juinjang.testutil.fixture.MemberFixture; - -@Slf4j -class PencilQueryServiceTest extends IntegrationTestSupport { - - @Autowired - private MemberRepository memberRepository; - - @Autowired - private AcquiredPencilRepository acquiredPencilRepository; - - @Autowired - private PurchasedPencilRepository purchasedPencilRepository; - - @Autowired - private UsedPencilRepository usedPencilRepository; - - @Autowired - private PencilQueryService pencilService; - - @AfterEach - void tearDown() { - purchasedPencilRepository.deleteAllInBatch(); - acquiredPencilRepository.deleteAllInBatch(); - usedPencilRepository.deleteAllInBatch(); - memberRepository.deleteAllInBatch(); - } - - @DisplayName("얻은 연필 목록이 없는 경우에는 빈 배열이 반환된다.") - @Test - void getEmptyAcquiredPencilsList() { - // given - Member member = MemberFixture.createDefaultMember(); - memberRepository.save(member); - - // when - List list = pencilService.getAcquiredPencils(member); - - // then - assertThat(list).hasSize(0); - } - - @DisplayName("얻은 연필 목록이 생성 시간(createdAt) 내림차순으로 정렬되어 반환된다.") - @Test - void getAcquiredPencilsOrderedByCreatedAtDesc() { - // given - Member member = MemberFixture.createDefaultMember(); - memberRepository.save(member); - - // 시간을 내림차순으로 생성 (최신 시간이 먼저 오도록) - LocalDateTime now = LocalDateTime.now(); - LocalDateTime time1 = now.minusHours(4); - LocalDateTime time2 = now.minusHours(3); - LocalDateTime time3 = now.minusHours(2); - LocalDateTime time4 = now.minusHours(1); - LocalDateTime time5 = now; - - // 명확한 순서로 데이터 생성 (시간 역순으로) - AcquiredPencil pencil1 = createAcquiredPencilWithTime(time1, "노트 작성으로 연필 획득", 1L, 10L, false, AcquiredType.NOTE, - member); - AcquiredPencil pencil2 = createAcquiredPencilWithTime(time2, "연필팩 구매로 연필 추가", 2L, 20L, true, AcquiredType.SOLD, - member); - AcquiredPencil pencil3 = createAcquiredPencilWithTime(time3, "매물 판매로 연필 획득", 3L, 30L, false, AcquiredType.SOLD, - member); - AcquiredPencil pencil4 = createAcquiredPencilWithTime(time4, "다른 노트 작성으로 연필 획득", 4L, 15L, true, - AcquiredType.NOTE, member); - AcquiredPencil pencil5 = createAcquiredPencilWithTime(time5, "또 다른 매물 판매로 연필 획득", 5L, 25L, false, - AcquiredType.SOLD, member); - - acquiredPencilRepository.saveAll(List.of(pencil1, pencil2, pencil3, pencil4, pencil5)); - - // when - List foundPencils = pencilService.getAcquiredPencils(member); - - foundPencils.forEach(pencil -> - log.info("[ACQUIRED PENCILS]: {}", pencil.getCreatedAt()) - ); - - // then - assertThat(foundPencils).hasSize(5) - .extracting("content", "sharedNoteId", "acquiredQuantity") - .containsExactly( - // 최신 시간부터 나열 (내림차순) - Tuple.tuple("또 다른 매물 판매로 연필 획득", 5L, 25L), - Tuple.tuple("다른 노트 작성으로 연필 획득", 4L, 15L), - Tuple.tuple("매물 판매로 연필 획득", 3L, 30L), - Tuple.tuple("연필팩 구매로 연필 추가", 2L, 20L), - Tuple.tuple("노트 작성으로 연필 획득", 1L, 10L) - ); - } - - @DisplayName("구매한 연필 목록이 없는 경우에는 빈 배열이 반환된다.") - @Test - void getEmptyPurchasedPencilsList() { - // given - Member member = MemberFixture.createDefaultMember(); - memberRepository.save(member); - - // when - List list = pencilService.getPurchasedPencils(member); - - // then - assertThat(list).hasSize(0); - } - - @DisplayName("구매한 연필 목록이 생성 시간(createdAt) 내림차순으로 정렬되어 반환된다.") - @Test - void getPurchasedPencilsOrderedByCreatedAtDesc() { - // given - Member member = MemberFixture.createDefaultMember(); - memberRepository.save(member); - - // 시간을 내림차순으로 생성 (최신 시간이 먼저 오도록) - LocalDateTime now = LocalDateTime.now(); - LocalDateTime time1 = now.minusHours(4); - LocalDateTime time2 = now.minusHours(3); - LocalDateTime time3 = now.minusHours(2); - LocalDateTime time4 = now.minusHours(1); - LocalDateTime time5 = now; - - // 랜덤 UUID 생성을 위한 도우미 - UUID uuid1 = UUID.randomUUID(); - UUID uuid2 = UUID.randomUUID(); - UUID uuid3 = UUID.randomUUID(); - UUID uuid4 = UUID.randomUUID(); - UUID uuid5 = UUID.randomUUID(); - - // 명확한 순서로 데이터 생성 (시간 역순으로) - PurchasedPencil pencil1 = PurchasedPencil.successOf(member, "10개 연필팩", 10L, 1000L, 0,"transaction1", uuid1, time1); - PurchasedPencil pencil2 = PurchasedPencil.successOf(member, "20개 연필팩", 20L, 2000L, 0,"transaction2", uuid2, time2); - PurchasedPencil pencil3 = PurchasedPencil.successOf(member, "30개 연필팩", 30L, 3000L,0 ,"transaction3", uuid3, time3); - PurchasedPencil pencil4 = PurchasedPencil.successOf(member, "15개 연필팩", 15L, 1500L, 0,"transaction4", uuid4, time4); - PurchasedPencil pencil5 = PurchasedPencil.successOf(member, "25개 연필팩", 25L, 2500L, 0,"transaction5", uuid5, time5); - - purchasedPencilRepository.saveAll(List.of(pencil1, pencil2, pencil3, pencil4, pencil5)); - - // when - List purchasedPencils = pencilService.getPurchasedPencils(member); - - purchasedPencils.forEach(pencil -> { - log.info("[PENCILS]: CREATED_AT : {} ", pencil.getPurchasedAt()); - } - ); - // then - assertThat(purchasedPencils).hasSize(5) - .extracting("title", "purchaseQuantity", "price") - .containsExactly( - Tuple.tuple("25개 연필팩", 25L, 2500L), - Tuple.tuple("15개 연필팩", 15L, 1500L), - Tuple.tuple("30개 연필팩", 30L, 3000L), - Tuple.tuple("20개 연필팩", 20L, 2000L), - Tuple.tuple("10개 연필팩", 10L, 1000L) - ); - } - - @DisplayName("구매한 연필 목록에서 DeliveryStatus 가 에러인 경우에만 목록에 반환되지 않는다.") - @Test - void getPurchasedPencilsWithoutDeliveryStatus() { - // given - Member member = MemberFixture.createDefaultMember(); - memberRepository.save(member); - - LocalDateTime time = LocalDateTime.now(); - UUID uuid = UUID.randomUUID(); - - PurchasedPencil pencil = PurchasedPencil.failedDueToServerError(member, "10개 연필팩", 10L, 1000L, 10,"transaction1", uuid, time); - - purchasedPencilRepository.saveAll(List.of(pencil)); - - // when - List purchasedPencils = pencilService.getPurchasedPencils(member); - - assertThat(purchasedPencils).hasSize(0); - } - - @DisplayName("구매한 연필 목록이 없는 경우에는 빈 배열이 반환된다.") - @Test - void getEmptyUsedPencilsList() { - // given - Member member = MemberFixture.createDefaultMember(); - memberRepository.save(member); - - // when - List list = pencilService.getUsedPencils(member); - - // then - assertThat(list).hasSize(0); - } - - @DisplayName("사용한 연필 목록이 생성 시간(createdAt) 내림차순으로 정렬되어 반환된다.") - @Test - void getUsedPencilsOrderedByCreatedAtDesc() { - // given - Member member = MemberFixture.createDefaultMember(); - memberRepository.save(member); - - UsedPencil usedPencil = UsedPencil.create(member, 1L, 10L, Usedtype.OWNED, "빌딩", 10L); - usedPencilRepository.saveAll(List.of(usedPencil)); - - // when - List usedPencils = pencilService.getUsedPencils(member); - - // then - // TODO: 추후에, 시간이 OrderBy 가 정상적으로 되는 지 테스트가 필요 - assertThat(usedPencils).hasSize(1) - .extracting("type", "buildingName", "sharedNoteId") - .containsExactly( - Tuple.tuple(Usedtype.OWNED, "빌딩", 1L) - ); - } - - @DisplayName("ConsumptionRequest가 PurchasedPencil 데이터를 기반으로 올바르게 생성된다.") - @Test - void getConsumptionRequestFromPurchasedPencil() { - // given - Member member = MemberFixture.createDefaultMember(); - memberRepository.save(member); - - LocalDateTime now = LocalDateTime.now(); - String transactionId = "test-transaction-id"; - UUID appAccountToken = UUID.randomUUID(); - - PurchasedPencil pencil = PurchasedPencil.successOf( - member, - "테스트 연필팩", - 20L, - 2000L, - 10, - transactionId, - appAccountToken, - now - ); - - purchasedPencilRepository.save(pencil); - - // AcquiredPencil 데이터를 하나라도 만들어줘야 sampleContentProvided == true - acquiredPencilRepository.save( - AcquiredPencil.create(member, "노트 작성", 1L, 10L, false, AcquiredType.NOTE) - ); - - // when - ConsumptionRequest request = pencilService.getConsumptionRequest(transactionId); - - // then - assertThat(request).isNotNull(); - assertThat(request.getAppAccountToken()).isEqualTo(appAccountToken); - assertThat(request.getDeliveryStatus().getValue()).isEqualTo(pencil.getDeliveryStatus().getAppleCode()); - assertThat(request.getPlayTime()).isEqualTo(PlayTime.FIVE_TO_SIXTY_MINUTES); - assertThat(request.getLifetimeDollarsPurchased()).isEqualTo(LifetimeDollarsPurchased.ONE_CENT_TO_FORTY_NINE_DOLLARS_AND_NINETY_NINE_CENTS); - assertThat(request.getLifetimeDollarsRefunded()).isEqualTo(LifetimeDollarsRefunded.ZERO_DOLLARS); - assertThat(request.getCustomerConsented()).isTrue(); - assertThat(request.getSampleContentProvided()).isTrue(); - assertThat(request.getPlatform()).isEqualTo(Platform.APPLE); - - assertThat(request.getConsumptionStatus()).isEqualTo(ConsumptionStatus.NOT_CONSUMED); - } - - - private AcquiredPencil createAcquiredPencilWithTime(LocalDateTime createdAt, String content, Long sharedNoteId, - Long acquiredQuantity, boolean isRead, AcquiredType type, Member member) { - return AcquiredPencil.createWithDate(member, content, sharedNoteId, acquiredQuantity, isRead, type, createdAt); - } - -} +// package umc.th.juinjang.api.pencil.service; +// +// import static org.assertj.core.api.Assertions.*; +// +// import java.time.LocalDateTime; +// import java.util.List; +// import java.util.UUID; +// +// import org.assertj.core.groups.Tuple; +// import org.junit.jupiter.api.AfterEach; +// import org.junit.jupiter.api.DisplayName; +// import org.junit.jupiter.api.Test; +// import org.springframework.beans.factory.annotation.Autowired; +// +// import com.apple.itunes.storekit.model.ConsumptionRequest; +// import com.apple.itunes.storekit.model.ConsumptionStatus; +// import com.apple.itunes.storekit.model.LifetimeDollarsPurchased; +// import com.apple.itunes.storekit.model.LifetimeDollarsRefunded; +// import com.apple.itunes.storekit.model.Platform; +// import com.apple.itunes.storekit.model.PlayTime; +// +// import lombok.extern.slf4j.Slf4j; +// import umc.th.juinjang.api.IntegrationTestSupport; +// import umc.th.juinjang.api.pencil.service.response.AcquiredPencilResponse; +// import umc.th.juinjang.api.pencil.service.response.PurchasedPencilResponse; +// import umc.th.juinjang.api.pencil.service.response.UsedPencilResponse; +// import umc.th.juinjang.domain.member.model.Member; +// import umc.th.juinjang.domain.member.repository.MemberRepository; +// import umc.th.juinjang.domain.pencil.acquired.model.AcquiredPencil; +// import umc.th.juinjang.domain.pencil.acquired.model.AcquiredType; +// import umc.th.juinjang.domain.pencil.acquired.repository.AcquiredPencilRepository; +// import umc.th.juinjang.domain.pencil.purchased.model.PurchasedPencil; +// import umc.th.juinjang.domain.pencil.purchased.repository.PurchasedPencilRepository; +// import umc.th.juinjang.domain.pencil.used.model.UsedPencil; +// import umc.th.juinjang.domain.pencil.used.model.Usedtype; +// import umc.th.juinjang.domain.pencil.used.repository.UsedPencilRepository; +// import umc.th.juinjang.testutil.fixture.MemberFixture; +// +// @Slf4j +// class PencilQueryServiceTest extends IntegrationTestSupport { +// +// @Autowired +// private MemberRepository memberRepository; +// +// @Autowired +// private AcquiredPencilRepository acquiredPencilRepository; +// +// @Autowired +// private PurchasedPencilRepository purchasedPencilRepository; +// +// @Autowired +// private UsedPencilRepository usedPencilRepository; +// +// @Autowired +// private PencilQueryService pencilService; +// +// @AfterEach +// void tearDown() { +// purchasedPencilRepository.deleteAllInBatch(); +// acquiredPencilRepository.deleteAllInBatch(); +// usedPencilRepository.deleteAllInBatch(); +// memberRepository.deleteAllInBatch(); +// } +// +// @DisplayName("얻은 연필 목록이 없는 경우에는 빈 배열이 반환된다.") +// @Test +// void getEmptyAcquiredPencilsList() { +// // given +// Member member = MemberFixture.createDefaultMember(); +// memberRepository.save(member); +// +// // when +// List list = pencilService.getAcquiredPencils(member); +// +// // then +// assertThat(list).hasSize(0); +// } +// +// @DisplayName("얻은 연필 목록이 생성 시간(createdAt) 내림차순으로 정렬되어 반환된다.") +// @Test +// void getAcquiredPencilsOrderedByCreatedAtDesc() { +// // given +// Member member = MemberFixture.createDefaultMember(); +// memberRepository.save(member); +// +// // 시간을 내림차순으로 생성 (최신 시간이 먼저 오도록) +// LocalDateTime now = LocalDateTime.now(); +// LocalDateTime time1 = now.minusHours(4); +// LocalDateTime time2 = now.minusHours(3); +// LocalDateTime time3 = now.minusHours(2); +// LocalDateTime time4 = now.minusHours(1); +// LocalDateTime time5 = now; +// +// // 명확한 순서로 데이터 생성 (시간 역순으로) +// AcquiredPencil pencil1 = createAcquiredPencilWithTime(time1, "노트 작성으로 연필 획득", 1L, 10L, false, AcquiredType.NOTE, +// member); +// AcquiredPencil pencil2 = createAcquiredPencilWithTime(time2, "연필팩 구매로 연필 추가", 2L, 20L, true, AcquiredType.SOLD, +// member); +// AcquiredPencil pencil3 = createAcquiredPencilWithTime(time3, "매물 판매로 연필 획득", 3L, 30L, false, AcquiredType.SOLD, +// member); +// AcquiredPencil pencil4 = createAcquiredPencilWithTime(time4, "다른 노트 작성으로 연필 획득", 4L, 15L, true, +// AcquiredType.NOTE, member); +// AcquiredPencil pencil5 = createAcquiredPencilWithTime(time5, "또 다른 매물 판매로 연필 획득", 5L, 25L, false, +// AcquiredType.SOLD, member); +// +// acquiredPencilRepository.saveAll(List.of(pencil1, pencil2, pencil3, pencil4, pencil5)); +// +// // when +// List foundPencils = pencilService.getAcquiredPencils(member); +// +// foundPencils.forEach(pencil -> +// log.info("[ACQUIRED PENCILS]: {}", pencil.getCreatedAt()) +// ); +// +// // then +// assertThat(foundPencils).hasSize(5) +// .extracting("content", "sharedNoteId", "acquiredQuantity") +// .containsExactly( +// // 최신 시간부터 나열 (내림차순) +// Tuple.tuple("또 다른 매물 판매로 연필 획득", 5L, 25L), +// Tuple.tuple("다른 노트 작성으로 연필 획득", 4L, 15L), +// Tuple.tuple("매물 판매로 연필 획득", 3L, 30L), +// Tuple.tuple("연필팩 구매로 연필 추가", 2L, 20L), +// Tuple.tuple("노트 작성으로 연필 획득", 1L, 10L) +// ); +// } +// +// @DisplayName("구매한 연필 목록이 없는 경우에는 빈 배열이 반환된다.") +// @Test +// void getEmptyPurchasedPencilsList() { +// // given +// Member member = MemberFixture.createDefaultMember(); +// memberRepository.save(member); +// +// // when +// List list = pencilService.getPurchasedPencils(member); +// +// // then +// assertThat(list).hasSize(0); +// } +// +// @DisplayName("구매한 연필 목록이 생성 시간(createdAt) 내림차순으로 정렬되어 반환된다.") +// @Test +// void getPurchasedPencilsOrderedByCreatedAtDesc() { +// // given +// Member member = MemberFixture.createDefaultMember(); +// memberRepository.save(member); +// +// // 시간을 내림차순으로 생성 (최신 시간이 먼저 오도록) +// LocalDateTime now = LocalDateTime.now(); +// LocalDateTime time1 = now.minusHours(4); +// LocalDateTime time2 = now.minusHours(3); +// LocalDateTime time3 = now.minusHours(2); +// LocalDateTime time4 = now.minusHours(1); +// LocalDateTime time5 = now; +// +// // 랜덤 UUID 생성을 위한 도우미 +// UUID uuid1 = UUID.randomUUID(); +// UUID uuid2 = UUID.randomUUID(); +// UUID uuid3 = UUID.randomUUID(); +// UUID uuid4 = UUID.randomUUID(); +// UUID uuid5 = UUID.randomUUID(); +// +// // 명확한 순서로 데이터 생성 (시간 역순으로) +// PurchasedPencil pencil1 = PurchasedPencil.successOf(member, "10개 연필팩", 10L, 1000L, 10L, 0, "transaction1", +// uuid1, time1); +// PurchasedPencil pencil2 = PurchasedPencil.successOf(member, "20개 연필팩", 20L, 2000L, 30L, 0, "transaction2", +// uuid2, +// time2); +// PurchasedPencil pencil3 = PurchasedPencil.successOf(member, "30개 연필팩", 30L, 3000L, 60L, 0, "transaction3", +// uuid3, +// time3); +// PurchasedPencil pencil4 = PurchasedPencil.successOf(member, "15개 연필팩", 15L, 1500L, 75L, 0, "transaction4", +// uuid4, +// time4); +// PurchasedPencil pencil5 = PurchasedPencil.successOf(member, "25개 연필팩", 25L, 2500L, 90L, 0, "transaction5", +// uuid5, +// time5); +// +// purchasedPencilRepository.saveAll(List.of(pencil1, pencil2, pencil3, pencil4, pencil5)); +// +// // when +// List purchasedPencils = pencilService.getPurchasedPencils(member); +// +// purchasedPencils.forEach(pencil -> { +// log.info("[PENCILS]: CREATED_AT : {} ", pencil.getPurchasedAt()); +// } +// ); +// // then +// assertThat(purchasedPencils).hasSize(5) +// .extracting("title", "purchaseQuantity", "price") +// .containsExactly( +// Tuple.tuple("25개 연필팩", 25L, 2500L), +// Tuple.tuple("15개 연필팩", 15L, 1500L), +// Tuple.tuple("30개 연필팩", 30L, 3000L), +// Tuple.tuple("20개 연필팩", 20L, 2000L), +// Tuple.tuple("10개 연필팩", 10L, 1000L) +// ); +// } +// +// @DisplayName("구매한 연필 목록에서 DeliveryStatus 가 에러인 경우에만 목록에 반환되지 않는다.") +// @Test +// void getPurchasedPencilsWithoutDeliveryStatus() { +// // given +// Member member = MemberFixture.createDefaultMember(); +// memberRepository.save(member); +// +// LocalDateTime time = LocalDateTime.now(); +// UUID uuid = UUID.randomUUID(); +// +// PurchasedPencil pencil = PurchasedPencil.failedDueToServerError(member, "10개 연필팩", 10L, 1000L, 10, +// "transaction1", uuid, time); +// +// purchasedPencilRepository.saveAll(List.of(pencil)); +// +// // when +// List purchasedPencils = pencilService.getPurchasedPencils(member); +// +// assertThat(purchasedPencils).hasSize(0); +// } +// +// @DisplayName("구매한 연필 목록이 없는 경우에는 빈 배열이 반환된다.") +// @Test +// void getEmptyUsedPencilsList() { +// // given +// Member member = MemberFixture.createDefaultMember(); +// memberRepository.save(member); +// +// // when +// List list = pencilService.getUsedPencils(member); +// +// // then +// assertThat(list).hasSize(0); +// } +// +// @DisplayName("사용한 연필 목록이 생성 시간(createdAt) 내림차순으로 정렬되어 반환된다.") +// @Test +// void getUsedPencilsOrderedByCreatedAtDesc() { +// // given +// Member member = MemberFixture.createDefaultMember(); +// memberRepository.save(member); +// +// UsedPencil usedPencil = UsedPencil.create(member, 1L, 10L, Usedtype.OWNED, "빌딩", 10L); +// usedPencilRepository.saveAll(List.of(usedPencil)); +// +// // when +// List usedPencils = pencilService.getUsedPencils(member); +// +// // then +// // TODO: 추후에, 시간이 OrderBy 가 정상적으로 되는 지 테스트가 필요 +// assertThat(usedPencils).hasSize(1) +// .extracting("type", "buildingName", "sharedNoteId") +// .containsExactly( +// Tuple.tuple(Usedtype.OWNED, "빌딩", 1L) +// ); +// } +// +// @DisplayName("ConsumptionRequest가 PurchasedPencil 데이터를 기반으로 올바르게 생성된다.") +// @Test +// void getConsumptionRequestFromPurchasedPencil() { +// // given +// Member member = MemberFixture.createDefaultMember(); +// memberRepository.save(member); +// +// LocalDateTime now = LocalDateTime.now(); +// String transactionId = "test-transaction-id"; +// UUID appAccountToken = UUID.randomUUID(); +// +// PurchasedPencil pencil = PurchasedPencil.successOf( +// member, +// "테스트 연필팩", +// 20L, +// 2000L, +// 10L, +// 10, +// transactionId, +// appAccountToken, +// now +// ); +// +// purchasedPencilRepository.save(pencil); +// +// // AcquiredPencil 데이터를 하나라도 만들어줘야 sampleContentProvided == true +// acquiredPencilRepository.save( +// AcquiredPencil.create(member, "노트 작성", 1L, 10L, 10L, false, AcquiredType.NOTE) +// ); +// +// // when +// ConsumptionRequest request = pencilService.getConsumptionRequest(transactionId); +// +// // then +// assertThat(request).isNotNull(); +// assertThat(request.getAppAccountToken()).isEqualTo(appAccountToken); +// assertThat(request.getDeliveryStatus().getValue()).isEqualTo(pencil.getDeliveryStatus().getAppleCode()); +// assertThat(request.getPlayTime()).isEqualTo(PlayTime.FIVE_TO_SIXTY_MINUTES); +// assertThat(request.getLifetimeDollarsPurchased()).isEqualTo( +// LifetimeDollarsPurchased.ONE_CENT_TO_FORTY_NINE_DOLLARS_AND_NINETY_NINE_CENTS); +// assertThat(request.getLifetimeDollarsRefunded()).isEqualTo(LifetimeDollarsRefunded.ZERO_DOLLARS); +// assertThat(request.getCustomerConsented()).isTrue(); +// assertThat(request.getSampleContentProvided()).isTrue(); +// assertThat(request.getPlatform()).isEqualTo(Platform.APPLE); +// +// assertThat(request.getConsumptionStatus()).isEqualTo(ConsumptionStatus.NOT_CONSUMED); +// } +// +// private AcquiredPencil createAcquiredPencilWithTime(LocalDateTime createdAt, String content, Long sharedNoteId, +// Long acquiredQuantity, boolean isRead, AcquiredType type, Member member) { +// return AcquiredPencil.createWithDate(member, content, sharedNoteId, acquiredQuantity, acquiredQuantity, isRead, +// type, createdAt); +// } +// +// }