diff --git a/src/docs/asciidoc/demo-day.adoc b/src/docs/asciidoc/demo-day.adoc index 31def15..c5a9264 100644 --- a/src/docs/asciidoc/demo-day.adoc +++ b/src/docs/asciidoc/demo-day.adoc @@ -35,3 +35,14 @@ include::{snippets}/read-demo-day-list/http-response.adoc[] ==== 인증되지 않은 유저가 활성화된 데모데이 목록을 조회할 경우 include::{snippets}/fail-read-demo-day-with-unauthenticated-user/response-fields.adoc[] include::{snippets}/fail-read-demo-day-with-unauthenticated-user/http-response.adoc[] + +== Demo Day Apply + +=== Request +include::{snippets}/apply-demo-day/request-headers.adoc[] +include::{snippets}/apply-demo-day/http-request.adoc[] + +=== Response + +==== 정상적으로 데모데이를 신청한 경우 +include::{snippets}/apply-demo-day/http-response.adoc[] diff --git a/src/main/java/dayone/dayone/demoday/entity/DemoDay.java b/src/main/java/dayone/dayone/demoday/entity/DemoDay.java index f44a5cd..8910fe7 100644 --- a/src/main/java/dayone/dayone/demoday/entity/DemoDay.java +++ b/src/main/java/dayone/dayone/demoday/entity/DemoDay.java @@ -1,10 +1,12 @@ package dayone.dayone.demoday.entity; -import dayone.dayone.demoday.entity.value.Capacity; import dayone.dayone.demoday.entity.value.DemoDate; import dayone.dayone.demoday.entity.value.RegistrationDate; import dayone.dayone.demoday.entity.value.Status; +import dayone.dayone.demoday.exception.DemoDayErrorCode; +import dayone.dayone.demoday.exception.DemoDayException; import dayone.dayone.global.entity.BaseEntity; +import dayone.dayone.user.entity.User; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; @@ -20,6 +22,7 @@ import java.time.LocalDateTime; import java.time.LocalTime; import java.time.temporal.ChronoUnit; +import java.util.Objects; @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @@ -36,9 +39,6 @@ public class DemoDay extends BaseEntity { private String thumbnail; - @Embedded - private Capacity capacity; - @Embedded private RegistrationDate registrationDate; @@ -57,7 +57,6 @@ public DemoDay( final String title, final String description, final String thumbnail, - final Capacity capacity, final RegistrationDate registrationDate, final DemoDate demoDate, final String location, @@ -68,7 +67,6 @@ public DemoDay( this.title = title; this.description = description; this.thumbnail = thumbnail; - this.capacity = capacity; this.registrationDate = registrationDate; this.demoDate = demoDate; this.location = location; @@ -84,7 +82,6 @@ public static DemoDay forSave( final String thumbnail, final LocalDate demoDate, final LocalTime demoTime, - final int capacity, final String location, final Long userId ) { @@ -93,7 +90,6 @@ public static DemoDay forSave( title, description, thumbnail, - new Capacity(capacity), RegistrationDate.of(demoDate, demoTime), DemoDate.of(demoDate, demoTime), location, @@ -102,6 +98,30 @@ public static DemoDay forSave( ); } + public DemoDayUser apply(final User user, final Ticket ticket, final DemoDayUsers demoDayUsers) { + validateApply(user, ticket, demoDayUsers); + ticket.sold(); + return new DemoDayUser(null, this.id, user.getId()); + } + + private void validateApply(final User user, final Ticket ticket, final DemoDayUsers demoDayUsers) { + if (this.status == Status.CLOSED) { + throw new DemoDayException(DemoDayErrorCode.DEMO_DAY_IS_CLOSED); + } + + if (Objects.equals(user.getId(), this.userId)) { + throw new DemoDayException(DemoDayErrorCode.DEMO_DAY_OWNER_NOT_APPLY_ONESELF); + } + + if (ticket.getCapacity() == 0) { + throw new DemoDayException(DemoDayErrorCode.DEMO_DAY_IS_FULL); + } + + if (demoDayUsers.isAlreadyApply(user.getId())) { + throw new DemoDayException(DemoDayErrorCode.DEMO_DAY_ALREADY_APPLY); + } + } + public LocalDateTime getDemoDate() { return demoDate.getDemoDate(); } diff --git a/src/main/java/dayone/dayone/demoday/entity/DemoDayUser.java b/src/main/java/dayone/dayone/demoday/entity/DemoDayUser.java new file mode 100644 index 0000000..da35f2e --- /dev/null +++ b/src/main/java/dayone/dayone/demoday/entity/DemoDayUser.java @@ -0,0 +1,36 @@ +package dayone.dayone.demoday.entity; + +import dayone.dayone.global.entity.BaseEntity; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Objects; + +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@Entity +public class DemoDayUser extends BaseEntity { + + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Id + private Long id; + + private Long demoDayId; + + private Long userId; + + public DemoDayUser(final Long id, final Long demoDayId, final Long userId) { + this.id = id; + this.demoDayId = demoDayId; + this.userId = userId; + this.createdAt = LocalDateTime.now().truncatedTo(ChronoUnit.MICROS); + this.updatedAt = LocalDateTime.now().truncatedTo(ChronoUnit.MICROS); + } +} diff --git a/src/main/java/dayone/dayone/demoday/entity/DemoDayUsers.java b/src/main/java/dayone/dayone/demoday/entity/DemoDayUsers.java new file mode 100644 index 0000000..ceab002 --- /dev/null +++ b/src/main/java/dayone/dayone/demoday/entity/DemoDayUsers.java @@ -0,0 +1,17 @@ +package dayone.dayone.demoday.entity; + +import java.util.List; + +public class DemoDayUsers { + + private final List demoDayUsers; + + public DemoDayUsers(final List demoDayUsers) { + this.demoDayUsers = demoDayUsers; + } + + public boolean isAlreadyApply(final Long userId) { + return demoDayUsers.stream() + .anyMatch(demoDayUser -> demoDayUser.getUserId().equals(userId)); + } +} diff --git a/src/main/java/dayone/dayone/demoday/entity/Ticket.java b/src/main/java/dayone/dayone/demoday/entity/Ticket.java new file mode 100644 index 0000000..f5be9b8 --- /dev/null +++ b/src/main/java/dayone/dayone/demoday/entity/Ticket.java @@ -0,0 +1,56 @@ +package dayone.dayone.demoday.entity; + +import dayone.dayone.demoday.entity.value.Capacity; +import dayone.dayone.global.entity.BaseEntity; +import jakarta.persistence.ConstraintMode; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.ForeignKey; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToOne; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; + +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@Entity +public class Ticket extends BaseEntity { + + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Id + private Long id; + + @OneToOne + @JoinColumn(name = "demo_day_id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT)) + private DemoDay demoDay; + + @Embedded + private Capacity capacity; + + public Ticket(final Long id, final DemoDay demoDay, final Capacity capacity) { + this.id = id; + this.demoDay = demoDay; + this.capacity = capacity; + this.createdAt = LocalDateTime.now().truncatedTo(ChronoUnit.MICROS); + this.updatedAt = LocalDateTime.now().truncatedTo(ChronoUnit.MICROS); + } + + public static Ticket forSave(final DemoDay demoDay, final int capacity) { + return new Ticket(null, demoDay, new Capacity(capacity)); + } + + public void sold() { + this.capacity.minus(); + } + + public int getCapacity() { + return capacity.getValue(); + } +} diff --git a/src/main/java/dayone/dayone/demoday/entity/respository/DemoDayRepository.java b/src/main/java/dayone/dayone/demoday/entity/respository/DemoDayRepository.java index 59d186d..2f13842 100644 --- a/src/main/java/dayone/dayone/demoday/entity/respository/DemoDayRepository.java +++ b/src/main/java/dayone/dayone/demoday/entity/respository/DemoDayRepository.java @@ -7,5 +7,5 @@ import java.util.List; public interface DemoDayRepository extends JpaRepository { - List findAllByStatus(Status status); + List findAllByStatus(final Status status); } diff --git a/src/main/java/dayone/dayone/demoday/entity/respository/DemoDayUserRepository.java b/src/main/java/dayone/dayone/demoday/entity/respository/DemoDayUserRepository.java new file mode 100644 index 0000000..02a8da9 --- /dev/null +++ b/src/main/java/dayone/dayone/demoday/entity/respository/DemoDayUserRepository.java @@ -0,0 +1,13 @@ +package dayone.dayone.demoday.entity.respository; + +import dayone.dayone.demoday.entity.DemoDayUser; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface DemoDayUserRepository extends JpaRepository { + Optional findByDemoDayIdAndUserId(final Long demoDayId, final Long userId); + + List findByDemoDayId(final Long demoDayId); +} diff --git a/src/main/java/dayone/dayone/demoday/entity/respository/TicketRepository.java b/src/main/java/dayone/dayone/demoday/entity/respository/TicketRepository.java new file mode 100644 index 0000000..bb2a141 --- /dev/null +++ b/src/main/java/dayone/dayone/demoday/entity/respository/TicketRepository.java @@ -0,0 +1,11 @@ +package dayone.dayone.demoday.entity.respository; + +import dayone.dayone.demoday.entity.Ticket; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface TicketRepository extends JpaRepository { + + Optional findByDemoDayId(final Long demoDayId); +} diff --git a/src/main/java/dayone/dayone/demoday/entity/value/Capacity.java b/src/main/java/dayone/dayone/demoday/entity/value/Capacity.java index 87e0893..050efcc 100644 --- a/src/main/java/dayone/dayone/demoday/entity/value/Capacity.java +++ b/src/main/java/dayone/dayone/demoday/entity/value/Capacity.java @@ -23,6 +23,10 @@ public Capacity(final int value) { this.value = value; } + public void minus() { + this.value--; + } + private void validate(final int value) { if (value < CAPACITY_MIN_VALUE) { throw new DemoDayException(DemoDayErrorCode.DEMO_DAY_CAPACITY_UNDER_ZERO); diff --git a/src/main/java/dayone/dayone/demoday/exception/DemoDayErrorCode.java b/src/main/java/dayone/dayone/demoday/exception/DemoDayErrorCode.java index 9620b8c..08a7bf7 100644 --- a/src/main/java/dayone/dayone/demoday/exception/DemoDayErrorCode.java +++ b/src/main/java/dayone/dayone/demoday/exception/DemoDayErrorCode.java @@ -7,7 +7,12 @@ public enum DemoDayErrorCode implements ErrorCode { DEMO_DAY_REGISTRATION_PERIOD_ERROR(HttpStatus.BAD_REQUEST, 5001, "신청 마감 시간이 신청 시작 시간보다 빠를 수 없습니다."), DEMO_DAY_IS_NOT_BEFORE_NOW(HttpStatus.BAD_REQUEST, 5002, "데모데이 날이 지금보다 이전일 수 없습니다."), - DEMO_DAY_CAPACITY_UNDER_ZERO(HttpStatus.BAD_REQUEST, 5003, "데모데이 참여 인원은 0명이거나 음수가 될 수 없습니다."); + DEMO_DAY_CAPACITY_UNDER_ZERO(HttpStatus.BAD_REQUEST, 5003, "데모데이 참여 인원은 0명이거나 음수가 될 수 없습니다."), + NOT_EXIST_DEMO_DAY(HttpStatus.NOT_FOUND, 5004, "데모데이가 존재하지 않습니다."), + DEMO_DAY_IS_CLOSED(HttpStatus.BAD_REQUEST, 5005, "데모데이가 종료되었습니다."), + DEMO_DAY_OWNER_NOT_APPLY_ONESELF(HttpStatus.BAD_REQUEST, 5006, "자신이 등록한 데모데이에는 신청할 수 없습니다."), + DEMO_DAY_IS_FULL(HttpStatus.BAD_REQUEST, 5007, "데모데이가 참여 인원이 만료되었습니다."), + DEMO_DAY_ALREADY_APPLY(HttpStatus.BAD_REQUEST, 5008, "이미 등록된 데모데이에 신청할 수 없습니다."); private final HttpStatus httpStatus; private final int code; diff --git a/src/main/java/dayone/dayone/demoday/service/DemoDayService.java b/src/main/java/dayone/dayone/demoday/service/DemoDayService.java index 6447b90..e268eb1 100644 --- a/src/main/java/dayone/dayone/demoday/service/DemoDayService.java +++ b/src/main/java/dayone/dayone/demoday/service/DemoDayService.java @@ -1,10 +1,17 @@ package dayone.dayone.demoday.service; import dayone.dayone.demoday.entity.DemoDay; +import dayone.dayone.demoday.entity.DemoDayUser; +import dayone.dayone.demoday.entity.DemoDayUsers; +import dayone.dayone.demoday.entity.Ticket; import dayone.dayone.demoday.entity.respository.DemoDayRepository; +import dayone.dayone.demoday.entity.respository.DemoDayUserRepository; +import dayone.dayone.demoday.entity.respository.TicketRepository; import dayone.dayone.demoday.entity.value.Status; +import dayone.dayone.demoday.exception.DemoDayErrorCode; import dayone.dayone.demoday.service.dto.DemoDayCreateRequest; import dayone.dayone.demoday.service.dto.DemoDayListResponse; +import dayone.dayone.user.entity.User; import dayone.dayone.user.entity.repository.UserRepository; import dayone.dayone.user.exception.UserErrorCode; import dayone.dayone.user.exception.UserException; @@ -20,6 +27,8 @@ public class DemoDayService { private final DemoDayRepository demoDayRepository; + private final TicketRepository ticketRepository; + private final DemoDayUserRepository demoDayUserRepository; private final UserRepository userRepository; @Transactional @@ -32,11 +41,12 @@ public Long create(final Long userId, final DemoDayCreateRequest request) { request.thumbnail(), request.demoDate(), request.demoTime(), - request.capacity(), request.location(), userId); demoDayRepository.save(demoDay); + final Ticket ticket = Ticket.forSave(demoDay, request.capacity()); + ticketRepository.save(ticket); return demoDay.getId(); } @@ -44,4 +54,18 @@ public DemoDayListResponse getDemoDaysWithStatus(final String status) { final List demoDays = demoDayRepository.findAllByStatus(Status.valueOf(status)); return DemoDayListResponse.from(demoDays); } + + @Transactional + public void applyDemoDay(final Long userId, final Long demoDayId) { + final User user = userRepository.findById(userId) + .orElseThrow(() -> new UserException(UserErrorCode.NOT_EXIST_USER)); + final DemoDay demoDay = demoDayRepository.findById(demoDayId) + .orElseThrow(() -> new UserException(DemoDayErrorCode.NOT_EXIST_DEMO_DAY)); + final Ticket ticket = ticketRepository.findByDemoDayId(demoDayId) + .orElseThrow(() -> new UserException(DemoDayErrorCode.NOT_EXIST_DEMO_DAY)); + final List demoDayUsers = demoDayUserRepository.findByDemoDayId(demoDayId); + + final DemoDayUser apply = demoDay.apply(user, ticket, new DemoDayUsers(demoDayUsers)); + demoDayUserRepository.save(apply); + } } diff --git a/src/main/java/dayone/dayone/demoday/ui/DemoDayController.java b/src/main/java/dayone/dayone/demoday/ui/DemoDayController.java index 60bfb8f..2b4bbf4 100644 --- a/src/main/java/dayone/dayone/demoday/ui/DemoDayController.java +++ b/src/main/java/dayone/dayone/demoday/ui/DemoDayController.java @@ -9,6 +9,7 @@ import org.springframework.data.repository.query.Param; import org.springframework.http.ResponseEntity; 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; import org.springframework.web.bind.annotation.RequestMapping; @@ -34,4 +35,10 @@ public CommonResponseDto getDemoDays(@Param("status") final final DemoDayListResponse response = demoDayService.getDemoDaysWithStatus(status); return CommonResponseDto.forSuccess(1, "데모데이 조회 성공", response); } + + @PostMapping("/{demoDayId}/apply") + public CommonResponseDto applyDemoDay(@AuthUser final Long userId, @PathVariable("demoDayId") final Long demoDayId) { + demoDayService.applyDemoDay(userId, demoDayId); + return CommonResponseDto.forSuccess(1, "데모데이 신청 성공", null); + } } diff --git a/src/test/java/dayone/dayone/demoday/docs/DemoDayDocsTest.java b/src/test/java/dayone/dayone/demoday/docs/DemoDayDocsTest.java index 8d60e58..0556139 100644 --- a/src/test/java/dayone/dayone/demoday/docs/DemoDayDocsTest.java +++ b/src/test/java/dayone/dayone/demoday/docs/DemoDayDocsTest.java @@ -19,6 +19,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willDoNothing; import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; @@ -31,6 +32,7 @@ import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -185,4 +187,33 @@ void readDemoDayWithUnAuthenticatedUser() throws Exception { )); } } + + @DisplayName("데모데이 신청 요청") + @Nested + class ApplyDemoDay { + @DisplayName("데모데이를 신청한다.") + @Test + void applyDemoDay() throws Exception { + // given + willDoNothing().given(demoDayService).applyDemoDay(anyLong(), anyLong()); + successAuth(); + + // when + final ResultActions result = mockMvc.perform(post("/api/v1/demo-days/{demoDayId}/apply", 1L) + .header(HttpHeaders.AUTHORIZATION, "Bearer accessToken")); + + // then + result.andExpect(status().isOk()) + .andDo(document("apply-demo-day", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestHeaders( + headerWithName(HttpHeaders.AUTHORIZATION).description("인증된 사용자의 accessToken") + ), + pathParameters( + parameterWithName("demoDayId").description("신청할 데모데이의 id") + ) + )); + } + } } diff --git a/src/test/java/dayone/dayone/demoday/entity/DemoDayTest.java b/src/test/java/dayone/dayone/demoday/entity/DemoDayTest.java new file mode 100644 index 0000000..90b357e --- /dev/null +++ b/src/test/java/dayone/dayone/demoday/entity/DemoDayTest.java @@ -0,0 +1,147 @@ +package dayone.dayone.demoday.entity; + +import dayone.dayone.demoday.entity.value.DemoDate; +import dayone.dayone.demoday.entity.value.RegistrationDate; +import dayone.dayone.demoday.entity.value.Status; +import dayone.dayone.demoday.exception.DemoDayErrorCode; +import dayone.dayone.demoday.exception.DemoDayException; +import dayone.dayone.user.entity.User; +import dayone.dayone.user.entity.value.Role; +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class DemoDayTest { + + @DisplayName("데모데이를 신청하면 티켓이 하나 감소한다.") + @Test + void applyDemoDay() { + // given + final LocalDateTime localDateTime = LocalDateTime.now(); + final DemoDay demoDayOpen = new DemoDay(null, + "title", + "description", + "이미지", + new RegistrationDate(localDateTime, localDateTime.plusDays(1)), + new DemoDate(localDateTime.plusDays(1)), + "장소", + Status.OPEN, + 1L); + final Ticket ticket = Ticket.forSave(demoDayOpen, 1); + final User user = new User(2L, "test@test.com", "test", "test", 1, "프로필", Role.MEMBER); + + // when + final DemoDayUser apply = demoDayOpen.apply(user, ticket, new DemoDayUsers(List.of())); + + // then + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(ticket.getCapacity()).isEqualTo(0); + softly.assertThat(apply.getUserId()).isEqualTo(user.getId()); + softly.assertThat(apply.getDemoDayId()).isEqualTo(demoDayOpen.getId()); + }); + } + + @DisplayName("이미 종료된 데모데이를 신청하면 예외를 발생한다.") + @Test + void applyDemoDayWithClosedDemoDay() { + // given + final LocalDateTime localDateTime = LocalDateTime.now(); + final DemoDay demoDayClosed = new DemoDay(null, + "title", + "description", + "이미지", + new RegistrationDate(localDateTime, localDateTime.plusDays(1)), + new DemoDate(localDateTime.plusDays(1)), + "장소", + Status.CLOSED, + 1L); + + final Ticket ticket = Ticket.forSave(demoDayClosed, 1); + final User user = new User(2L, "test@test.com", "test", "test", 1, "프로필", Role.MEMBER); + + // when + // then + assertThatThrownBy(() -> demoDayClosed.apply(user, ticket, new DemoDayUsers(List.of()))) + .isInstanceOf(DemoDayException.class) + .hasMessage(DemoDayErrorCode.DEMO_DAY_IS_CLOSED.getMessage()); + } + + @DisplayName("데모데이 등록자는 자신의 데모데이를 신청할 수 없다.") + @Test + void applyDemoDayWithNotOwner() { + // given + final LocalDateTime localDateTime = LocalDateTime.now(); + final DemoDay demoDayOpen = new DemoDay(null, + "title", + "description", + "이미지", + new RegistrationDate(localDateTime, localDateTime.plusDays(1)), + new DemoDate(localDateTime.plusDays(1)), + "장소", + Status.OPEN, + 1L); + final Ticket ticket = Ticket.forSave(demoDayOpen, 1); + final User owner = new User(1L, "test@test.com", "test", "test", 1, "프로필", Role.MEMBER); + + // when + // then + assertThatThrownBy(() -> demoDayOpen.apply(owner, ticket, new DemoDayUsers(List.of()))) + .isInstanceOf(DemoDayException.class) + .hasMessage(DemoDayErrorCode.DEMO_DAY_OWNER_NOT_APPLY_ONESELF.getMessage()); + } + + @DisplayName("참여 인원이 꽉찬 경우 예외를 발생한다.") + @Test + void applyDemoDayWithOverCapacity() { + // given + final LocalDateTime localDateTime = LocalDateTime.now(); + final DemoDay demoDayOpen = new DemoDay(null, + "title", + "description", + "이미지", + new RegistrationDate(localDateTime, localDateTime.plusDays(1)), + new DemoDate(localDateTime.plusDays(1)), + "장소", + Status.OPEN, + 1L); + final Ticket ticket = Ticket.forSave(demoDayOpen, 1); + final User user = new User(2L, "test@test.com", "test", "test", 1, "프로필", Role.MEMBER); + ticket.sold(); + + // when + // then + assertThatThrownBy(() -> demoDayOpen.apply(user, ticket, new DemoDayUsers(List.of()))) + .isInstanceOf(DemoDayException.class) + .hasMessage(DemoDayErrorCode.DEMO_DAY_IS_FULL.getMessage()); + } + + @DisplayName("이미 신청한 데모데이에 신청할 경우 예외를 발생한다.") + @Test + void applyDemoDayAlreadyApply() { + // given + final LocalDateTime localDateTime = LocalDateTime.now(); + final DemoDay demoDayOpen = new DemoDay(null, + "title", + "description", + "이미지", + new RegistrationDate(localDateTime, localDateTime.plusDays(1)), + new DemoDate(localDateTime.plusDays(1)), + "장소", + Status.OPEN, + 1L); + final Ticket ticket = Ticket.forSave(demoDayOpen, 1); + final User user = new User(2L, "test@test.com", "test", "test", 1, "프로필", Role.MEMBER); + final DemoDayUsers demoDayUsers = new DemoDayUsers(List.of(new DemoDayUser(null, 1L, user.getId()))); + + // when + // then + assertThatThrownBy(() -> demoDayOpen.apply(user, ticket, demoDayUsers)) + .isInstanceOf(DemoDayException.class) + .hasMessage(DemoDayErrorCode.DEMO_DAY_ALREADY_APPLY.getMessage()); + } +} diff --git a/src/test/java/dayone/dayone/demoday/service/DemoDayServiceTest.java b/src/test/java/dayone/dayone/demoday/service/DemoDayServiceTest.java index 4e6aeda..d7a7632 100644 --- a/src/test/java/dayone/dayone/demoday/service/DemoDayServiceTest.java +++ b/src/test/java/dayone/dayone/demoday/service/DemoDayServiceTest.java @@ -1,12 +1,17 @@ package dayone.dayone.demoday.service; import dayone.dayone.demoday.entity.DemoDay; +import dayone.dayone.demoday.entity.DemoDayUser; +import dayone.dayone.demoday.entity.Ticket; import dayone.dayone.demoday.entity.respository.DemoDayRepository; +import dayone.dayone.demoday.entity.respository.DemoDayUserRepository; +import dayone.dayone.demoday.entity.respository.TicketRepository; import dayone.dayone.demoday.entity.value.Status; import dayone.dayone.demoday.service.dto.DemoDayCreateRequest; import dayone.dayone.demoday.service.dto.DemoDayListResponse; import dayone.dayone.demoday.service.dto.DemoDayResponse; import dayone.dayone.fixture.TestDemoDayFactory; +import dayone.dayone.fixture.TestTicketFactory; import dayone.dayone.fixture.TestUserFactory; import dayone.dayone.support.ServiceTest; import dayone.dayone.user.entity.User; @@ -21,6 +26,7 @@ import java.time.LocalDate; import java.time.LocalTime; import java.util.List; +import java.util.Optional; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -32,9 +38,18 @@ class DemoDayServiceTest extends ServiceTest { @Autowired private TestDemoDayFactory testDemoDayFactory; + @Autowired + private TestTicketFactory testTicketFactory; + @Autowired private DemoDayRepository demoDayRepository; + @Autowired + private TicketRepository ticketRepository; + + @Autowired + private DemoDayUserRepository demoDayUserRepository; + @Autowired private DemoDayService demoDayService; @@ -55,6 +70,7 @@ void createDemoDay() { // then final DemoDay demoDay = demoDayRepository.findById(savedId).get(); + final Ticket ticket = ticketRepository.findByDemoDayId(savedId).get(); SoftAssertions.assertSoftly(softAssertions -> { softAssertions.assertThat(demoDay.getTitle()).isEqualTo(request.title()); @@ -62,10 +78,10 @@ void createDemoDay() { softAssertions.assertThat(demoDay.getThumbnail()).isEqualTo(request.thumbnail()); softAssertions.assertThat(demoDay.getUserId()).isEqualTo(user.getId()); softAssertions.assertThat(demoDay.getLocation()).isEqualTo(request.location()); - softAssertions.assertThat(demoDay.getCapacity().getValue()).isEqualTo(request.capacity()); softAssertions.assertThat(demoDay.getRegistrationDate().getStartRegistrationDate().toLocalDate()).isEqualTo(today); softAssertions.assertThat(demoDay.getRegistrationDate().getEndRegistrationDate().toLocalDate()).isEqualTo(tomorrow); softAssertions.assertThat(demoDay.getDemoDate().toLocalDate()).isEqualTo(tomorrow); + softAssertions.assertThat(ticket.getCapacity()).isEqualTo(request.capacity()); }); } @@ -110,4 +126,29 @@ void readDemoDayByStatus() { }); } } + + @DisplayName("데모데이 신청") + @Nested + class apply { + @DisplayName("데모데이를 신청하면 티켓이 하나 감소한다.") + @Test + void applyDemoDay() { + // given + final User DemoDayOwner = testUserFactory.createUser("test@test.com", "test", "test", 1); + final User DemoDayUser = testUserFactory.createUser("test2@test.com", "test2", "test2", 1); + final DemoDay demoDayOpen = testDemoDayFactory.createDemoDayOpen("title", "description", DemoDayOwner.getId()); + testTicketFactory.createNTicket(demoDayOpen, 1); + + // when + demoDayService.applyDemoDay(DemoDayUser.getId(), demoDayOpen.getId()); + + // then + final Optional byDemoDayIdAndUserId = demoDayUserRepository.findByDemoDayIdAndUserId(demoDayOpen.getId(), DemoDayUser.getId()); + final Ticket ticket = ticketRepository.findByDemoDayId(demoDayOpen.getId()).get(); + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(ticket.getCapacity()).isEqualTo(0); + softly.assertThat(byDemoDayIdAndUserId.isPresent()).isTrue(); + }); + } + } } diff --git a/src/test/java/dayone/dayone/fixture/TestDemoDayFactory.java b/src/test/java/dayone/dayone/fixture/TestDemoDayFactory.java index 612dea4..c092f7a 100644 --- a/src/test/java/dayone/dayone/fixture/TestDemoDayFactory.java +++ b/src/test/java/dayone/dayone/fixture/TestDemoDayFactory.java @@ -2,7 +2,6 @@ import dayone.dayone.demoday.entity.DemoDay; import dayone.dayone.demoday.entity.respository.DemoDayRepository; -import dayone.dayone.demoday.entity.value.Capacity; import dayone.dayone.demoday.entity.value.DemoDate; import dayone.dayone.demoday.entity.value.RegistrationDate; import dayone.dayone.demoday.entity.value.Status; @@ -19,16 +18,30 @@ public class TestDemoDayFactory { @Autowired private DemoDayRepository demoDayRepository; + public DemoDay createDemoDayOpen(final String title, final String description, final Long userId) { + final LocalDateTime localDateTime = LocalDateTime.now(); + final DemoDay demoDay = new DemoDay(null, + title, + description, + "이미지", + new RegistrationDate(localDateTime, localDateTime.plusDays(1)), + new DemoDate(localDateTime.plusDays(1)), + "장소", + Status.OPEN, + userId); + demoDayRepository.save(demoDay); + return demoDay; + } + public List createNDemoDaysWithStatus(final int n, final String title, final String description, final Status status) { List demoDays = new ArrayList<>(); for (int i = 0; i < n; i++) { - LocalDateTime localDateTime = LocalDateTime.now(); + final LocalDateTime localDateTime = LocalDateTime.now(); final DemoDay demoDay = new DemoDay(null, title, description, "이미지", - new Capacity(10), new RegistrationDate(localDateTime, localDateTime.plusDays(1)), new DemoDate(localDateTime.plusDays(1)), "장소", diff --git a/src/test/java/dayone/dayone/fixture/TestTicketFactory.java b/src/test/java/dayone/dayone/fixture/TestTicketFactory.java new file mode 100644 index 0000000..701f90e --- /dev/null +++ b/src/test/java/dayone/dayone/fixture/TestTicketFactory.java @@ -0,0 +1,19 @@ +package dayone.dayone.fixture; + +import dayone.dayone.demoday.entity.DemoDay; +import dayone.dayone.demoday.entity.Ticket; +import dayone.dayone.demoday.entity.respository.TicketRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class TestTicketFactory { + + @Autowired + private TicketRepository ticketRepository; + + public Ticket createNTicket(final DemoDay demoDay, final int capacity) { + final Ticket ticket = Ticket.forSave(demoDay, capacity); + return ticketRepository.save(ticket); + } +}