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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public CreateReservationResponse updateReservation(
public void cancel(Long reservationId, Long memberId) {
Reservation reservation = reservationDomainService.getByIdAndMemberId(reservationId, memberId);
AvailableDate availableDate = reservation.getAvailableDate();
availableDate.increaseCapacity(reservation.getPartySize());
restaurantDomainService.increaseAvailableDateCapacity(availableDate, reservation.getPartySize());
reservation.cancel();
Comment on lines +78 to 79
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

용량 복원 실패 여부 확인 로직이 없습니다

increaseAvailableDateCapacity가 내부적으로 업데이트된 행 수를 반환한다면 그 값을 검사하여 동시 취소 충돌(이미 취소됐거나 만석 해제 실패) 여부를 판단해야 합니다. 현재는 void 호출 후 바로 reservation.cancel()만 수행하므로 실제 용량이 복원되지 못해도 일관성이 깨질 수 있습니다.
예외 처리를 추가하거나 반환값 기반 검증으로 보강해 주세요.


증가 로직 실패 여부 검증을 위해 restaurantDomainService.increaseAvailableDateCapacity 메소드 정의를 확인합니다.


🏁 Script executed:

#!/bin/bash
rg "increaseAvailableDateCapacity" -n -A3 -B3

Length of output: 2858


더 깊은 검증을 위해 increaseCapacity의 시그니처를 확인합니다.


🏁 Script executed:

#!/bin/bash
rg "increaseCapacity" -n -A3 -B3 domain/src/main/java/com/wellmeet/domain/restaurant

Length of output: 2445


용량 복원 실패 검증 로직 추가 필요

increaseAvailableDateCapacity는 내부적으로 void 메소드(AvailableDateRepository.increaseCapacity)만 호출하여 반환값이 없어, 실제 업데이트 실패(동시 취소 충돌 등)를 알 수 없습니다. 반면 decreaseCapacity는 업데이트된 행 수를 반환하여 실패 여부를 검증하고 있습니다.

다음 위치를 참고하여 복원 실패 검증 로직을 추가해 주세요:

  • ReservationService.java (api-user/src/main/java/com/wellmeet/reservation/ReservationService.java)
    • cancel 메소드 78행: restaurantDomainService.increaseAvailableDateCapacity(...) 호출 직후 복원 성공 여부 점검
  • RestaurantDomainService.java (domain/src/main/java/com/wellmeet/domain/restaurant/RestaurantDomainService.java)
    • increaseAvailableDateCapacity를 반환값 있는 형태로 변경하거나 예외를 던지도록 수정
  • AvailableDateRepository.java (domain/src/main/java/com/wellmeet/domain/restaurant/availabledate/repository/AvailableDateRepository.java)
    • @Modifying 쿼리를 int increaseCapacity(...)로 변경하고, 호출부에서 반환값 > 0 여부 검증
- void increaseCapacity(@Param("id") Long id, @Param("partySize") int partySize);
+ @Modifying
+ @Query(...)
+ int increaseCapacity(@Param("id") Long id, @Param("partySize") int partySize);

그 후 ReservationService.cancel() 흐름에서 반환된 행 수를 확인하여 실패 시 예외 처리하거나 롤백 로직을 적용해 주세요.

🤖 Prompt for AI Agents
In api-user/src/main/java/com/wellmeet/reservation/ReservationService.java
around lines 78-79, the call to
restaurantDomainService.increaseAvailableDateCapacity does not verify if the
capacity restoration succeeded because the method returns void. To fix this,
modify AvailableDateRepository.increaseCapacity to return an int indicating the
number of updated rows, update
RestaurantDomainService.increaseAvailableDateCapacity to return this int or
throw an exception on failure, and then in ReservationService.cancel(), check
the returned value after calling increaseAvailableDateCapacity; if the update
failed (return value <= 0), throw an exception or trigger rollback to handle the
failure properly.

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.wellmeet.domain.restaurant.availabledate.entity.AvailableDate;
import com.wellmeet.domain.restaurant.entity.Restaurant;
import com.wellmeet.reservation.dto.CreateReservationRequest;
import com.wellmeet.reservation.dto.CreateReservationResponse;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
Expand Down Expand Up @@ -75,4 +76,123 @@ class Reserve {
);
}
}

@Nested
class UpdateReservation {

@Test
void 같은_예약시간의_인원수를_변경할_수_있다() {
Owner owner1 = ownerGenerator.generate("owner1");
Restaurant restaurant1 = restaurantGenerator.generate("restaurant1", owner1);
int capacity = 16;
AvailableDate availableDate1 = availableDateGenerator.generate(LocalDateTime.now().plusDays(1), capacity,
restaurant1);
int partySize = 4;
Member member1 = memberGenerator.generate("member1");
CreateReservationRequest createRequest1 = new CreateReservationRequest(
restaurant1.getId(), availableDate1.getId(), partySize, "request"
);
CreateReservationResponse reserve1 = reservationService.reserve(member1.getId(), createRequest1);
int changePartySize = 7;
CreateReservationRequest request1 = new CreateReservationRequest(
restaurant1.getId(), availableDate1.getId(), changePartySize, "request"
);

reservationService.updateReservation(
reserve1.getId(), member1.getId(), request1
);
List<Reservation> reservations = reservationRepository.findAll();
AvailableDate foundAvailableDate1 = availableDateRepository.findById(availableDate1.getId()).get();

assertAll(
() -> assertThat(reservations).hasSize(1),
() -> assertThat(foundAvailableDate1.getMaxCapacity()).isEqualTo(capacity - changePartySize)
);
}

@Test
void 한_사람이_업데이트_요청을_동시에_여러개_보내도_한_번만_처리된다() throws InterruptedException {
Owner owner1 = ownerGenerator.generate("owner1");
Restaurant restaurant1 = restaurantGenerator.generate("restaurant1", owner1);
int capacity = 50;
AvailableDate availableDate1 = availableDateGenerator.generate(LocalDateTime.now().plusDays(1), capacity,
restaurant1);
AvailableDate availableDate2 = availableDateGenerator.generate(LocalDateTime.now().plusDays(2), capacity,
restaurant1);
int partySize = 4;
Member member1 = memberGenerator.generate("member1");
CreateReservationRequest createRequest1 = new CreateReservationRequest(
restaurant1.getId(), availableDate1.getId(), partySize, "request"
);
CreateReservationResponse reserve1 = reservationService.reserve(member1.getId(), createRequest1);
int changePartySize = 7;
CreateReservationRequest request1 = new CreateReservationRequest(
restaurant1.getId(), availableDate2.getId(), changePartySize, "request"
);

runAtSameTime(10, () -> reservationService.updateReservation(
reserve1.getId(), member1.getId(), request1
));
List<Reservation> reservations = reservationRepository.findAll();
AvailableDate foundAvailableDate1 = availableDateRepository.findById(availableDate1.getId()).get();
AvailableDate foundAvailableDate2 = availableDateRepository.findById(availableDate2.getId()).get();

assertAll(
() -> assertThat(reservations).hasSize(1),
() -> assertThat(foundAvailableDate1.getMaxCapacity()).isEqualTo(capacity),
() -> assertThat(foundAvailableDate2.getMaxCapacity()).isEqualTo(capacity - changePartySize)
);
}

@Test
void 여러_사람이_업데이트_요청을_동시에_여러개가_보내도_적절히_처리된다() throws InterruptedException {
Owner owner1 = ownerGenerator.generate("owner1");
Restaurant restaurant1 = restaurantGenerator.generate("restaurant1", owner1);
int capacity = 16;
AvailableDate availableDate1 = availableDateGenerator.generate(LocalDateTime.now().plusDays(1), capacity,
restaurant1);
AvailableDate availableDate2 = availableDateGenerator.generate(LocalDateTime.now().plusDays(2), capacity,
restaurant1);
AvailableDate availableDate3 = availableDateGenerator.generate(LocalDateTime.now().plusDays(3), capacity,
restaurant1);
int partySize = 4;
Member member1 = memberGenerator.generate("member1");
Member member2 = memberGenerator.generate("member2");
CreateReservationRequest createRequest1 = new CreateReservationRequest(
restaurant1.getId(), availableDate1.getId(), partySize, "request"
);
CreateReservationRequest createRequest2 = new CreateReservationRequest(
restaurant1.getId(), availableDate2.getId(), partySize, "request"
);
CreateReservationResponse reserve1 = reservationService.reserve(member1.getId(), createRequest1);
CreateReservationResponse reserve2 = reservationService.reserve(member2.getId(), createRequest2);
int changePartySize = 7;
CreateReservationRequest request1 = new CreateReservationRequest(
restaurant1.getId(), availableDate3.getId(), changePartySize, "request"
);
CreateReservationRequest request2 = new CreateReservationRequest(
restaurant1.getId(), availableDate3.getId(), changePartySize, "request"
);
List<Runnable> tasks = new ArrayList<>();
tasks.add(() -> reservationService.updateReservation(
reserve1.getId(), member1.getId(), request1
));
tasks.add(() -> reservationService.updateReservation(
reserve2.getId(), member2.getId(), request2
));

runAtSameTime(tasks);
List<Reservation> reservations = reservationRepository.findAll();
AvailableDate foundAvailableDate1 = availableDateRepository.findById(availableDate1.getId()).get();
AvailableDate foundAvailableDate2 = availableDateRepository.findById(availableDate2.getId()).get();
AvailableDate foundAvailableDate3 = availableDateRepository.findById(availableDate3.getId()).get();

assertAll(
() -> assertThat(reservations).hasSize(2),
() -> assertThat(foundAvailableDate1.getMaxCapacity()).isEqualTo(capacity),
() -> assertThat(foundAvailableDate2.getMaxCapacity()).isEqualTo(capacity),
() -> assertThat(foundAvailableDate3.getMaxCapacity()).isEqualTo(capacity - changePartySize * 2)
);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.wellmeet.domain.common;

import org.hibernate.exception.ConstraintViolationException;
import org.springframework.dao.DataIntegrityViolationException;

public class RepositoryErrorDecoder {

private static final String UNIQUE_RESERVATION_ERROR_MESSAGE = "unique_member_restaurant_available_date";

private RepositoryErrorDecoder() {
}

public static boolean isUniqueConstraintViolation(DataIntegrityViolationException exception) {
Throwable cause = exception.getCause();
while (cause != null) {
if (cause instanceof ConstraintViolationException cve) {
return UNIQUE_RESERVATION_ERROR_MESSAGE.equals(cve.getConstraintName());
}
cause = cause.getCause();
}
return false;
Comment on lines +13 to +21
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

유니크 위반 감지가 Hibernate 전용에 치우침 — SQLState/에러코드 기반 fallback 추가 권장

드라이버/DB에 따라 cause 체인이 ConstraintViolationException이 아닐 수도 있고, constraintName이 null/다르게 세팅될 수 있습니다. SQLState(예: 23000, 23505)와 MySQL 에러코드(1062) 기반의 보강 로직을 추가해 내구성을 높여주세요.

아래처럼 보강을 제안합니다(핵심 변경만 표시):

-    public static boolean isUniqueConstraintViolation(DataIntegrityViolationException exception) {
-        Throwable cause = exception.getCause();
-        while (cause != null) {
-            if (cause instanceof ConstraintViolationException cve) {
-                return UNIQUE_RESERVATION_ERROR_MESSAGE.equals(cve.getConstraintName());
-            }
-            cause = cause.getCause();
-        }
-        return false;
-    }
+    public static boolean isUniqueConstraintViolation(DataIntegrityViolationException exception) {
+        Throwable cause = exception;
+        while (cause != null) {
+            if (cause instanceof ConstraintViolationException cve) {
+                if (UNIQUE_RESERVATION_CONSTRAINT_NAME.equals(cve.getConstraintName())) {
+                    return true;
+                }
+                if (isUniqueViolation(cve.getSQLException())) {
+                    return true;
+                }
+            } else if (cause instanceof SQLException sqlEx) {
+                if (isUniqueViolation(sqlEx)) {
+                    return true;
+                }
+            }
+            cause = cause.getCause();
+        }
+        return false;
+    }

추가로 필요한 보조 코드(선택 위치에 추가):

// import 추가
import java.sql.SQLException;

// helper 추가
private static boolean isUniqueViolation(SQLException ex) {
    if (ex == null) return false;
    String sqlState = ex.getSQLState();
    int errorCode = ex.getErrorCode();
    return "23000".equals(sqlState)   // ANSI/Oracle/MySQL: integrity constraint violation
        || "23505".equals(sqlState)   // PostgreSQL: unique_violation
        || errorCode == 1062;         // MySQL: Duplicate entry
}
🤖 Prompt for AI Agents
In domain/src/main/java/com/wellmeet/domain/common/RepositoryErrorDecoder.java
around lines 13 to 21, the unique constraint violation detection relies solely
on Hibernate's ConstraintViolationException and constraintName, which may be
null or absent depending on the DB/driver. Enhance robustness by adding fallback
logic that checks SQLState codes ("23000", "23505") and MySQL error code (1062)
from any SQLException in the cause chain. Implement a helper method
isUniqueViolation(SQLException ex) to encapsulate this logic and modify the
existing method to return true if either the constraintName matches or the
SQLState/errorCode indicates a unique violation.

}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package com.wellmeet.domain.reservation;

import com.wellmeet.domain.common.RepositoryErrorDecoder;
import com.wellmeet.domain.reservation.entity.Reservation;
import com.wellmeet.domain.reservation.exception.ReservationErrorCode;
import com.wellmeet.domain.reservation.exception.ReservationException;
import com.wellmeet.domain.reservation.repository.ReservationRepository;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;

@Service
Expand All @@ -15,7 +17,14 @@ public class ReservationDomainService {
private final ReservationRepository reservationRepository;

public Reservation save(Reservation reservation) {
return reservationRepository.save(reservation);
try {
return reservationRepository.save(reservation);
} catch (DataIntegrityViolationException exception) {
if (RepositoryErrorDecoder.isUniqueConstraintViolation(exception)) {
throw new ReservationException(ReservationErrorCode.ALREADY_RESERVED);
}
throw exception;
}
Comment on lines 19 to +27
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

제약 위반이 flush/commit 시점에 발생하면 현재 catch에 걸리지 않습니다 — @transactional + flush 보장 필요

JPA/Hibernate는 실제 DB 제약 위반을 flush/commit 시 던집니다. 현재 save()가 flush를 보장하지 않으면 DataIntegrityViolationException이 메서드 밖(트랜잭션 커밋 시점)에서 발생해 도메인 예외로 변환되지 않을 수 있습니다.

권장 수정(Repository가 JpaRepository인 경우):

@@
-    public Reservation save(Reservation reservation) {
-        try {
-            return reservationRepository.save(reservation);
+    @Transactional
+    public Reservation save(Reservation reservation) {
+        try {
+            return reservationRepository.saveAndFlush(reservation);
         } catch (DataIntegrityViolationException exception) {
             if (RepositoryErrorDecoder.isUniqueConstraintViolation(exception)) {
                 throw new ReservationException(ReservationErrorCode.ALREADY_RESERVED);
             }
             throw exception;
         }
     }
  • 추가 import 필요: import org.springframework.transaction.annotation.Transactional;

만약 saveAndFlush를 사용할 수 없다면, 같은 트랜잭션 내에서 save(..)flush()를 호출해 예외를 이 메서드 범위로 당겨오세요:

@Transactional
public Reservation save(Reservation reservation) {
    try {
        reservationRepository.save(reservation);
        reservationRepository.flush(); // 혹은 EntityManager.flush()
        return reservation;
    } catch (DataIntegrityViolationException e) {
        if (RepositoryErrorDecoder.isUniqueConstraintViolation(e)) {
            throw new ReservationException(ReservationErrorCode.ALREADY_RESERVED);
        }
        throw e;
    }
}

ReservationRepositoryJpaRepository를 상속하는지 확인(가능하면 saveAndFlush 사용):


🏁 Script executed:

#!/bin/bash
rg -n "interface\s+ReservationRepository" -A5
rg -n "extends\s+JpaRepository<" -A1

Length of output: 3002


트랜잭션 범위 내에서 제약 위반 예외를 안전하게 처리하도록 save 메서드 수정 필요

  • 파일: domain/src/main/java/com/wellmeet/domain/reservation/ReservationDomainService.java
    대상 메서드: save(Reservation reservation) (19~27행)
  • ReservationRepositoryJpaRepository를 상속하므로 saveAndFlush() 사용 가능

제안하는 변경사항:

@@
-    public Reservation save(Reservation reservation) {
+    @Transactional
+    public Reservation save(Reservation reservation) {
         try {
-            return reservationRepository.save(reservation);
+            return reservationRepository.saveAndFlush(reservation);
         } catch (DataIntegrityViolationException exception) {
             if (RepositoryErrorDecoder.isUniqueConstraintViolation(exception)) {
                 throw new ReservationException(ReservationErrorCode.ALREADY_RESERVED);
             }
             throw exception;
         }
     }

추가 import:

import org.springframework.transaction.annotation.Transactional;
🤖 Prompt for AI Agents
In
domain/src/main/java/com/wellmeet/domain/reservation/ReservationDomainService.java
around lines 19 to 27, modify the save method to handle constraint violation
exceptions safely within a transaction scope by adding the @Transactional
annotation to the method and replacing reservationRepository.save(reservation)
with reservationRepository.saveAndFlush(reservation). Also, add the import for
org.springframework.transaction.annotation.Transactional at the top of the file.

}

public Reservation getByIdAndMemberId(Long reservationId, Long memberId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,21 @@
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDateTime;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Table(
name = "reservation",
uniqueConstraints = @UniqueConstraint(
name = "unique_member_restaurant_available_date",
columnNames = {"member_id", "restaurant_id", "available_date_id"}
)
)
Comment on lines +24 to +30
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

제약 이름 지정으로 저장 시 예외 분기 안정화 — 구현 가이드 제안.

이름이 부여된 유니크 제약(“unique_member_restaurant_available_date”)은 저장 시 중복 예약을 명확히 식별하는 데 유용합니다. 메시지 파싱 대신 제약 이름으로 분기하세요.

  • Spring/Hibernate 예외 매핑 예시(서비스/어댑터 계층):
import org.springframework.dao.DataIntegrityViolationException;
import org.hibernate.exception.ConstraintViolationException;

public void createReservation(...) {
    try {
        reservationRepository.save(reservation);
    } catch (DataIntegrityViolationException ex) {
        ConstraintViolationException cve = unwrapConstraintViolation(ex);
        if (cve != null && "unique_member_restaurant_available_date".equals(cve.getConstraintName())) {
            // 동일 회원·식당·시간대 중복 예약
            throw new ReservationException(ReservationErrorCode.DUPLICATE_RESERVATION);
        }
        // 그 외 DB 오류는 상위 핸들러로
        throw ex;
    }
}

private ConstraintViolationException unwrapConstraintViolation(Throwable t) {
    while (t != null) {
        if (t instanceof ConstraintViolationException cve) return cve;
        t = t.getCause();
    }
    return null;
}
  • 추가 권고:
    • 동시성 경합이 높은 구간에서는 DeadlockLoserDataAccessException/Lock wait timeout 대비 재시도 정책(예: Spring Retry, 지수 백오프) 적용을 권장합니다.

원하시면 공통 예외 변환기/리트라이 어드바이스를 만들어 드릴게요.

다음 스크립트로 코드베이스에 이미 제약 이름 기반 분기가 있는지 확인하세요.


🏁 Script executed:

#!/bin/bash
rg -n "unique_member_restaurant_available_date|ConstraintViolationException|DeadlockLoserDataAccessException|ER_DUP_ENTRY" -S

Length of output: 877


서비스 계층에 제약 이름 기반 예외 처리를 추가해주세요.

현재 코드베이스에서 DB 제약 위반(“unique_member_restaurant_available_date”)을 잡아내는 로직은 없고, GlobalExceptionHandler는 Bean Validation용 jakarta.validation.ConstraintViolationException만 처리하고 있습니다. 따라서 저장 시 중복 예약을 명확히 식별하고 재시도 정책을 적용하려면 아래를 적용하는 것을 권장드립니다.

  • 처리 대상

    • ReservationService#createReservation(...) (또는 예약 저장을 담당하는 서비스/어댑터 클래스)
  • 구현 예시

 public void createReservation(Reservation reservation) {
-   reservationRepository.save(reservation);
+   try {
+       reservationRepository.save(reservation);
+   } catch (DataIntegrityViolationException ex) {
+       ConstraintViolationException cve = unwrapConstraintViolation(ex);
+       if (cve != null && "unique_member_restaurant_available_date".equals(cve.getConstraintName())) {
+           throw new ReservationException(ReservationErrorCode.DUPLICATE_RESERVATION);
+       }
+       throw ex;
+   }
 }

 private ConstraintViolationException unwrapConstraintViolation(Throwable t) {
     while (t != null) {
         if (t instanceof ConstraintViolationException cve) return cve;
         t = t.getCause();
     }
     return null;
 }
  • 동시성 경합 구간에 한정해, DeadlockLoserDataAccessException이나 LockTimeoutException 발생 시 재시도 정책(예: Spring Retry + 지수 백오프)도 적용하면 안정성이 높아집니다.

필요하다면 공통 예외 변환기 또는 @retryable 어드바이스 구현을 도와드릴 수 있습니다.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In domain/src/main/java/com/wellmeet/domain/reservation/entity/Reservation.java
around lines 24 to 30, the unique constraint
"unique_member_restaurant_available_date" is defined but there is no service
layer logic to handle exceptions caused by violating this constraint. To fix
this, add exception handling in the ReservationService#createReservation method
to catch database exceptions related to this unique constraint violation and
throw a meaningful custom exception. Additionally, implement a retry mechanism
with exponential backoff for concurrency-related exceptions like
DeadlockLoserDataAccessException or LockTimeoutException to improve reliability
during concurrent reservation attempts.

@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,4 @@ public AvailableDate(LocalDate date, LocalTime time, int maxCapacity, Restaurant
public boolean canNotReserve(int partySize) {
return !isAvailable || maxCapacity < partySize;
}

public void increaseCapacity(int partySize) {
maxCapacity += partySize;
isAvailable = true;
}
}
3 changes: 2 additions & 1 deletion domain/src/main/resources/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ CREATE TABLE IF NOT EXISTS reservation
FOREIGN KEY (restaurant_id) REFERENCES restaurant (id),
FOREIGN KEY (available_date_id) REFERENCES available_date (id),
FOREIGN KEY (member_id) REFERENCES member (id),
UNIQUE KEY unique_member_restaurant_available_date (member_id, restaurant_id, available_date_id),
CHECK (status IN ('PENDING', 'CONFIRMED', 'CANCELED')),
INDEX idx_reservation_restaurant (restaurant_id),
INDEX idx_reservation_member (member_id),
Expand Down Expand Up @@ -161,4 +162,4 @@ CREATE TABLE IF NOT EXISTS review_tag
updated_at DATETIME(6) NOT NULL,
FOREIGN KEY (review_id) REFERENCES review (id),
INDEX idx_review_tag_review (review_id)
);
);
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
package com.wellmeet.domain.restaurant.availabledate.entity;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertAll;

import com.wellmeet.domain.restaurant.exception.RestaurantErrorCode;
import com.wellmeet.domain.restaurant.exception.RestaurantException;
import java.time.LocalDate;
import java.time.LocalTime;
import org.junit.jupiter.api.Nested;
Expand All @@ -14,57 +10,21 @@
class AvailableDateTest {

@Nested
class ReserveParty {
class CanNotReserve {

@Test
void 예약_인원만큼_가용_인원이_줄어든다() {
void 예약_인원수가_가용인원보다_크면_예약이_불가능하다() {
int maxCapacity = 10;
AvailableDate availableDate = new AvailableDate(
LocalDate.now().plusDays(1),
LocalDate.now(),
LocalTime.now(),
maxCapacity,
null
);
int partySize = 5;
availableDate.reduceCapacity(partySize);
int partySize = maxCapacity + 1;
boolean canNotReserve = availableDate.canNotReserve(partySize);

assertAll(
() -> assertThat(availableDate.getMaxCapacity()).isEqualTo(maxCapacity - partySize),
() -> assertThat(availableDate.isAvailable()).isTrue()
);
}

@Test
void 가용_인원이_0이면_예약_불가능하다() {
int maxCapacity = 10;
AvailableDate availableDate = new AvailableDate(
LocalDate.now().plusDays(1),
LocalTime.now(),
maxCapacity,
null
);
int partySize = 10;
availableDate.reduceCapacity(partySize);

assertAll(
() -> assertThat(availableDate.getMaxCapacity()).isEqualTo(maxCapacity - partySize),
() -> assertThat(availableDate.isAvailable()).isFalse()
);
}

@Test
void 가용_인원_수보다_많은_인원이_예약할_수_없다() {
int maxCapacity = 10;
AvailableDate availableDate = new AvailableDate(
LocalDate.now().plusDays(1),
LocalTime.now(),
maxCapacity,
null
);

assertThatThrownBy(() -> availableDate.reduceCapacity(maxCapacity + 1))
.isInstanceOf(RestaurantException.class)
.hasMessage(RestaurantErrorCode.NOT_ENOUGH_CAPACITY.getMessage());
assertThat(canNotReserve).isTrue();
}
}
}
Loading