Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 경매는 재고보다 많은 양의 사용자 요청을 처리할 수 없다. #59

Merged
merged 5 commits into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class Auction {
private final String productName;
private long originPrice;
private long currentPrice;
private int stock;
private long stock;
private int maximumPurchaseLimitCount;
private PricePolicy pricePolicy;
private Duration variationDuration;
Expand All @@ -22,7 +22,7 @@ public class Auction {
@Builder
private Auction(final ZonedDateTime startedAt, final Long sellerId, final String productName,
final long originPrice,
final int stock,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

long 예리한데 👍

final long stock,
final int maximumPurchaseLimitCount, final PricePolicy pricePolicy,
final Duration variationDuration,
final ZonedDateTime finishedAt,
Expand Down Expand Up @@ -53,6 +53,24 @@ public AuctionStatus getStatus() {
return AuctionStatus.WAITING;
}

/**
* 해당 수량만큼 구매가 가능한지 확인한다.
*
* @param quantity 구매를 원하는 수량
* @return 구매가 가능한 경우 True, 구매가 불가능한 경우 False를 반환한다.
*/
public boolean canPurchase(long quantity) {
if (quantity <= 0) { // 구매 요청은 0이거나 더 작을 수 없다.
return false;
}

if (quantity > maximumPurchaseLimitCount) { // 인당 구매 수량 제한을 넘기지 않는지 확인한다.
return false;
}

return stock >= quantity; // 구매 요청 수량보다 재고가 많은지 확인한다.
}

public void update() {
// TODO 경매 옵션을 변경하는 로직 (Update)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,25 @@ public void changeOption(UpdateAuctionCommand command) {
auctionRepository.save(auction);
}

/**
* 경매 상품에 대한 입찰(구매)을 진행한다.
*
* @param auctionId 경매 번호
* @param price 구매를 원하는 가격
* @param quantity 수량
*/
public void submitBid(long auctionId, long price, long quantity) {
// 검증
Auction auction = auctionRepository.findById(auctionId)
.orElseThrow(() -> new NotFoundException("경매(Auction)를 찾을 수 없습니다. AuctionId: " + auctionId,
ErrorCode.A011));

if (!auction.canPurchase(quantity)) {
throw new BadRequestException(
"해당 수량만큼 구매할 수 없습니다. 재고: " + auction.getStock() + ", "
+ "요청: " + quantity + ", 인당구매제한: " + auction.getMaximumPurchaseLimitCount(), ErrorCode.A014);
}

// TODO 구매(입찰) 로직
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public enum ErrorCode {
A011("경매ID를 기준으로 경매를 찾으려고 했지만 찾을 수 없습니다."),
A012("이미 시작된 경매를 변경하려고 할 때, 예외가 발생합니다."),
A013("경매 정보 생성 시, 현재 가격은 0과 같거나 작을 경우 예외가 발생합니다."),
A014("요청 수량만큼 경매 상품 구입이 불가능 할 때 예외가 발생합니다."),

// Member 관련 예외 코드
M000("로그인(회원가입) 시, 이미 존재하는 회원 아이디로 로그인을 시도한 경우 예외가 발생합니다."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,4 +208,113 @@ void when_change_auction_that_is_started() {
ErrorCode.A012));
}
}

@Nested
@DisplayName("경매 입찰(구매)을 진행할 때")
class submitBidTest {
@Test
@DisplayName("요청을 정상적으로 처리한다.")
void success_case() {
// given
long auctionId = 1L;
long price = 10000L;
long quantity = 100L;

Auction auction = Auction.builder()
.startedAt(ZonedDateTime.now())
.finishedAt(ZonedDateTime.now().plusMinutes(10))
.sellerId(1L)
.productName("Test Product")
.originPrice(10000)
.stock(100)
.maximumPurchaseLimitCount(100)
.pricePolicy(new ConstantPricePolicy(1000))
.variationDuration(Duration.ofMinutes(1L))
.isShowStock(true)
.build();

// when
when(auctionRepository.findById(auctionId)).thenReturn(Optional.of(auction));

// then
assertThatNoException().isThrownBy(() -> auctionService.submitBid(auctionId, price, quantity));
}

@Test
@DisplayName("유효하지 않은 경매번호를 전달받은 경우 예외가 발생하고 에러 코드는 A011이다.")
void when_invalid_auction_id_should_throw_exception() {
// given
long auctionId = 1L;
long price = 10000L;
long quantity = 100L;

// when
when(auctionRepository.findById(auctionId)).thenReturn(Optional.empty());

// then
assertThatThrownBy(() -> auctionService.submitBid(auctionId, price, quantity))
.isInstanceOf(NotFoundException.class)
.satisfies(exception -> assertThat(exception).hasFieldOrPropertyWithValue("errorCode", ErrorCode.A011));
}

@Test
@DisplayName("경매 재고보다 많은 수량을 입찰(구매)하려는 경우 예외가 발생하고 에러 코드는 A014이다.")
void when_quantity_more_than_auction_stock_should_throw_exception() {
// given
long auctionId = 1L;
long price = 10000L;
long quantity = 100L;

Auction auction = Auction.builder()
.startedAt(ZonedDateTime.now())
.finishedAt(ZonedDateTime.now().plusMinutes(10))
.sellerId(1L)
.productName("Test Product")
.originPrice(10000)
.stock(50) // 경매 재고
.maximumPurchaseLimitCount(50)
.pricePolicy(new ConstantPricePolicy(1000))
.variationDuration(Duration.ofMinutes(1L))
.isShowStock(true)
.build();

// when
when(auctionRepository.findById(auctionId)).thenReturn(Optional.of(auction));

// then
assertThatThrownBy(() -> auctionService.submitBid(auctionId, price, quantity))
.isInstanceOf(BadRequestException.class)
.satisfies(exception -> assertThat(exception).hasFieldOrPropertyWithValue("errorCode", ErrorCode.A014));
}

@Test
@DisplayName("인당 구매 제한 수량보다 많은 수량을 입찰(구매)하려는 경우 예외가 발생하고 에러 코드는 A014이다.")
void when_quantity_more_than_maximum_pruchase_limit_should_throw_exception() {
// given
long auctionId = 1L;
long price = 10000L;
long quantity = 100L;

Auction auction = Auction.builder()
.startedAt(ZonedDateTime.now())
.finishedAt(ZonedDateTime.now().plusMinutes(10))
.sellerId(1L)
.productName("Test Product")
.originPrice(10000)
.stock(10000)
.maximumPurchaseLimitCount(10) // 인당 구매 수량 제한
.pricePolicy(new ConstantPricePolicy(1000))
.variationDuration(Duration.ofMinutes(1L))
.isShowStock(true)
.build();

// when
when(auctionRepository.findById(auctionId)).thenReturn(Optional.of(auction));

// then
assertThatThrownBy(() -> auctionService.submitBid(auctionId, price, quantity))
.isInstanceOf(BadRequestException.class)
.satisfies(exception -> assertThat(exception).hasFieldOrPropertyWithValue("errorCode", ErrorCode.A014));
}
}
}