From 312f2fab5a751ae28d3e067d04a2954623051e7d Mon Sep 17 00:00:00 2001 From: Ethan Date: Tue, 20 Feb 2024 11:06:35 +0900 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20DeadlineSheduler=20=EA=B5=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RunnerPostDeadlineCheckScheduler.java | 17 ------ .../deadline/command/DeadlineOutbox.java | 46 +++++++++++++++ .../event/TaskSchedulerEventListener.java | 57 +++++++++++++++++++ .../exception/DeadlineOutboxException.java | 10 ++++ .../exception/ScheduleBusinessException.java | 10 ++++ .../DeadlineOutboxCommandRepository.java | 17 ++++++ .../RunnerPostDeadlineCommandRepository.java} | 4 +- .../RunnerPostDeadlineCheckScheduler.java | 43 ++++++++++++++ .../RunnerPostDeadlineSchedulerService.java} | 4 +- .../RunnerPostDeadlineQueryRepository.java | 30 ++++++++++ .../java/touch/baton/config/AsyncConfig.java | 9 +++ .../domain/runnerpost/command/RunnerPost.java | 4 ++ .../common/fixture/DeadlineOutboxFixture.java | 18 ++++++ ...eduleRunnerPostCommandRepositoryTest.java} | 22 +++---- 14 files changed, 260 insertions(+), 31 deletions(-) delete mode 100644 backend/baton/src/main/java/touch/baton/common/schedule/RunnerPostDeadlineCheckScheduler.java create mode 100644 backend/baton/src/main/java/touch/baton/common/schedule/deadline/command/DeadlineOutbox.java create mode 100644 backend/baton/src/main/java/touch/baton/common/schedule/deadline/command/event/TaskSchedulerEventListener.java create mode 100644 backend/baton/src/main/java/touch/baton/common/schedule/deadline/command/exception/DeadlineOutboxException.java create mode 100644 backend/baton/src/main/java/touch/baton/common/schedule/deadline/command/exception/ScheduleBusinessException.java create mode 100644 backend/baton/src/main/java/touch/baton/common/schedule/deadline/command/repository/DeadlineOutboxCommandRepository.java rename backend/baton/src/main/java/touch/baton/common/schedule/{ScheduleRunnerPostRepository.java => deadline/command/repository/RunnerPostDeadlineCommandRepository.java} (77%) create mode 100644 backend/baton/src/main/java/touch/baton/common/schedule/deadline/command/service/RunnerPostDeadlineCheckScheduler.java rename backend/baton/src/main/java/touch/baton/common/schedule/{SchedulerService.java => deadline/command/service/RunnerPostDeadlineSchedulerService.java} (80%) create mode 100644 backend/baton/src/main/java/touch/baton/common/schedule/deadline/query/repository/RunnerPostDeadlineQueryRepository.java create mode 100644 backend/baton/src/main/java/touch/baton/config/AsyncConfig.java create mode 100644 backend/baton/src/test/java/touch/baton/common/fixture/DeadlineOutboxFixture.java rename backend/baton/src/test/java/touch/baton/common/schedule/{ScheduleRunnerPostQueryRepositoryTest.java => deadline/command/repository/ScheduleRunnerPostCommandRepositoryTest.java} (82%) diff --git a/backend/baton/src/main/java/touch/baton/common/schedule/RunnerPostDeadlineCheckScheduler.java b/backend/baton/src/main/java/touch/baton/common/schedule/RunnerPostDeadlineCheckScheduler.java deleted file mode 100644 index 296e569b1..000000000 --- a/backend/baton/src/main/java/touch/baton/common/schedule/RunnerPostDeadlineCheckScheduler.java +++ /dev/null @@ -1,17 +0,0 @@ -package touch.baton.common.schedule; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; - -@RequiredArgsConstructor -@Component -public class RunnerPostDeadlineCheckScheduler { - - private final ScheduleRunnerPostRepository scheduleRunnerPostRepository; - - @Transactional - public void updateReviewStatus() { - scheduleRunnerPostRepository.updateAllPassedDeadline(); - } -} diff --git a/backend/baton/src/main/java/touch/baton/common/schedule/deadline/command/DeadlineOutbox.java b/backend/baton/src/main/java/touch/baton/common/schedule/deadline/command/DeadlineOutbox.java new file mode 100644 index 000000000..8b9ea579c --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/common/schedule/deadline/command/DeadlineOutbox.java @@ -0,0 +1,46 @@ +package touch.baton.common.schedule.deadline.command; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import touch.baton.common.schedule.deadline.command.exception.DeadlineOutboxException; +import touch.baton.domain.common.BaseEntity; + +import java.time.Instant; + +import static jakarta.persistence.GenerationType.IDENTITY; +import static lombok.AccessLevel.PROTECTED; + +@Getter +@NoArgsConstructor(access = PROTECTED) +@Entity +public class DeadlineOutbox extends BaseEntity { + + @GeneratedValue(strategy = IDENTITY) + @Id + private Long id; + + private Long runnerPostId; + + private Instant instantToRun; + + @Builder + public DeadlineOutbox(final Long runnerPostId, final Instant instantToRun) { + validateNotNull(runnerPostId, instantToRun); + this.runnerPostId = runnerPostId; + this.instantToRun = instantToRun; + } + + private void validateNotNull(final Long runnerPostId, final Instant instantToRun) { + if (runnerPostId == null) { + throw new DeadlineOutboxException("DeadlineOutBox의 runnerPostId는 null이 될 수 없습니다."); + } + + if (instantToRun == null) { + throw new DeadlineOutboxException("DeadlineOutBox의 instantToRun은 null이 될 수 없습니다."); + } + } +} diff --git a/backend/baton/src/main/java/touch/baton/common/schedule/deadline/command/event/TaskSchedulerEventListener.java b/backend/baton/src/main/java/touch/baton/common/schedule/deadline/command/event/TaskSchedulerEventListener.java new file mode 100644 index 000000000..bb97bd8c9 --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/common/schedule/deadline/command/event/TaskSchedulerEventListener.java @@ -0,0 +1,57 @@ +package touch.baton.common.schedule.deadline.command.event; + +import lombok.RequiredArgsConstructor; +import org.springframework.boot.context.event.ApplicationStartedEvent; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.event.TransactionalEventListener; +import touch.baton.common.schedule.deadline.command.service.RunnerPostDeadlineCheckScheduler; +import touch.baton.common.schedule.deadline.command.DeadlineOutbox; +import touch.baton.common.schedule.deadline.command.repository.DeadlineOutboxCommandRepository; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.event.RunnerPostApplySupporterEvent; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; + +import static org.springframework.transaction.event.TransactionPhase.AFTER_COMMIT; + +@RequiredArgsConstructor +public class TaskSchedulerEventListener { + + private static final int GRACE_PERIODS_DAYS = 3; + + private final TaskScheduler taskScheduler; + private final RunnerPostDeadlineCheckScheduler runnerPostDeadlineCheckScheduler; + private final DeadlineOutboxCommandRepository deadlineOutboxCommandRepository; + + @TransactionalEventListener(phase = AFTER_COMMIT) + @Transactional + public void subscribeAppliedRunnerPostDeadline(final RunnerPostApplySupporterEvent event) { + final RunnerPost findRunnerPost = runnerPostDeadlineCheckScheduler.joinBySupporter(event.runnerPostId()); + final Instant instantToRun = calculateInstant(findRunnerPost); + + final DeadlineOutbox deadlineOutBox = DeadlineOutbox.builder() + .runnerPostId(event.runnerPostId()) + .instantToRun(instantToRun) + .build(); + deadlineOutboxCommandRepository.save(deadlineOutBox); + taskScheduler.schedule(() -> runnerPostDeadlineCheckScheduler.finishReview(event.runnerPostId()), instantToRun); + } + + private Instant calculateInstant(final RunnerPost findRunnerPost) { + final LocalDateTime gracePeriods = findRunnerPost.getDeadline().getValue().plusDays(GRACE_PERIODS_DAYS); + return gracePeriods.atZone(ZoneId.systemDefault()).toInstant(); + } + + @EventListener(ApplicationStartedEvent.class) + public void setUpSchedules() { + deadlineOutboxCommandRepository.findAll().forEach(deadlineOutbox -> + taskScheduler.schedule(() -> runnerPostDeadlineCheckScheduler.finishReview( + deadlineOutbox.getRunnerPostId()), deadlineOutbox.getInstantToRun() + )); + } +} + diff --git a/backend/baton/src/main/java/touch/baton/common/schedule/deadline/command/exception/DeadlineOutboxException.java b/backend/baton/src/main/java/touch/baton/common/schedule/deadline/command/exception/DeadlineOutboxException.java new file mode 100644 index 000000000..105125f81 --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/common/schedule/deadline/command/exception/DeadlineOutboxException.java @@ -0,0 +1,10 @@ +package touch.baton.common.schedule.deadline.command.exception; + +import touch.baton.domain.common.exception.BusinessException; + +public class DeadlineOutboxException extends BusinessException { + + public DeadlineOutboxException(final String message) { + super(message); + } +} diff --git a/backend/baton/src/main/java/touch/baton/common/schedule/deadline/command/exception/ScheduleBusinessException.java b/backend/baton/src/main/java/touch/baton/common/schedule/deadline/command/exception/ScheduleBusinessException.java new file mode 100644 index 000000000..b433bf848 --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/common/schedule/deadline/command/exception/ScheduleBusinessException.java @@ -0,0 +1,10 @@ +package touch.baton.common.schedule.deadline.command.exception; + +import touch.baton.domain.common.exception.BusinessException; + +public class ScheduleBusinessException extends BusinessException { + + public ScheduleBusinessException(final String message) { + super(message); + } +} diff --git a/backend/baton/src/main/java/touch/baton/common/schedule/deadline/command/repository/DeadlineOutboxCommandRepository.java b/backend/baton/src/main/java/touch/baton/common/schedule/deadline/command/repository/DeadlineOutboxCommandRepository.java new file mode 100644 index 000000000..16055bbc4 --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/common/schedule/deadline/command/repository/DeadlineOutboxCommandRepository.java @@ -0,0 +1,17 @@ +package touch.baton.common.schedule.deadline.command.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import touch.baton.common.schedule.deadline.command.DeadlineOutbox; + +public interface DeadlineOutboxCommandRepository extends JpaRepository { + + @Modifying(clearAutomatically = true, flushAutomatically = true) + @Query(""" + delete from DeadlineOutbox do + where do.runnerPostId = :id + """) + void deleteByRunnerPostId(@Param("id") Long runnerPostId); +} diff --git a/backend/baton/src/main/java/touch/baton/common/schedule/ScheduleRunnerPostRepository.java b/backend/baton/src/main/java/touch/baton/common/schedule/deadline/command/repository/RunnerPostDeadlineCommandRepository.java similarity index 77% rename from backend/baton/src/main/java/touch/baton/common/schedule/ScheduleRunnerPostRepository.java rename to backend/baton/src/main/java/touch/baton/common/schedule/deadline/command/repository/RunnerPostDeadlineCommandRepository.java index fc4007d90..09f1f7233 100644 --- a/backend/baton/src/main/java/touch/baton/common/schedule/ScheduleRunnerPostRepository.java +++ b/backend/baton/src/main/java/touch/baton/common/schedule/deadline/command/repository/RunnerPostDeadlineCommandRepository.java @@ -1,11 +1,11 @@ -package touch.baton.common.schedule; +package touch.baton.common.schedule.deadline.command.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import touch.baton.domain.runnerpost.command.RunnerPost; -public interface ScheduleRunnerPostRepository extends JpaRepository { +public interface RunnerPostDeadlineCommandRepository extends JpaRepository { @Modifying(clearAutomatically = true, flushAutomatically = true) @Query(""" diff --git a/backend/baton/src/main/java/touch/baton/common/schedule/deadline/command/service/RunnerPostDeadlineCheckScheduler.java b/backend/baton/src/main/java/touch/baton/common/schedule/deadline/command/service/RunnerPostDeadlineCheckScheduler.java new file mode 100644 index 000000000..c71509aba --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/common/schedule/deadline/command/service/RunnerPostDeadlineCheckScheduler.java @@ -0,0 +1,43 @@ +package touch.baton.common.schedule.deadline.command.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; +import touch.baton.common.schedule.deadline.command.repository.RunnerPostDeadlineCommandRepository; +import touch.baton.common.schedule.deadline.command.repository.DeadlineOutboxCommandRepository; +import touch.baton.common.schedule.deadline.command.exception.ScheduleBusinessException; +import touch.baton.common.schedule.deadline.query.repository.RunnerPostDeadlineQueryRepository; +import touch.baton.domain.runnerpost.command.RunnerPost; + +@RequiredArgsConstructor +@Transactional +@Component +public class RunnerPostDeadlineCheckScheduler { + + private final RunnerPostDeadlineCommandRepository runnerPostDeadlineCommandRepository; + private final RunnerPostDeadlineQueryRepository runnerPostDeadlineQueryRepository; + private final DeadlineOutboxCommandRepository deadlineOutboxCommandRepository; + + public void updateReviewStatus() { + runnerPostDeadlineCommandRepository.updateAllPassedDeadline(); + } + + @Transactional(readOnly = true) + public RunnerPost joinBySupporter(final Long runnerPostId) { + return runnerPostDeadlineQueryRepository.joinSupporterByRunnerPostId(runnerPostId) + .orElseThrow(() -> new ScheduleBusinessException(String.format("RunnerPost를 찾을 수 없습니다. runnerPostId=%s",runnerPostId))); + } + + @Async + public void finishReview(final Long runnerPostId) { + final RunnerPost findRunnerPost = runnerPostDeadlineQueryRepository.joinSupporterByRunnerPostIdWithLock(runnerPostId) + .orElseThrow(() -> new ScheduleBusinessException(String.format("RunnerPost를 찾을 수 없습니다. runnerPostId=%s",runnerPostId))); + if (findRunnerPost.isReviewStatusDone()) { + return; + } + + findRunnerPost.finishReview(); + deadlineOutboxCommandRepository.deleteByRunnerPostId(runnerPostId); + } +} diff --git a/backend/baton/src/main/java/touch/baton/common/schedule/SchedulerService.java b/backend/baton/src/main/java/touch/baton/common/schedule/deadline/command/service/RunnerPostDeadlineSchedulerService.java similarity index 80% rename from backend/baton/src/main/java/touch/baton/common/schedule/SchedulerService.java rename to backend/baton/src/main/java/touch/baton/common/schedule/deadline/command/service/RunnerPostDeadlineSchedulerService.java index b7bdd4f87..7ec5dcbec 100644 --- a/backend/baton/src/main/java/touch/baton/common/schedule/SchedulerService.java +++ b/backend/baton/src/main/java/touch/baton/common/schedule/deadline/command/service/RunnerPostDeadlineSchedulerService.java @@ -1,4 +1,4 @@ -package touch.baton.common.schedule; +package touch.baton.common.schedule.deadline.command.service; import lombok.RequiredArgsConstructor; import org.springframework.scheduling.annotation.Scheduled; @@ -6,7 +6,7 @@ @RequiredArgsConstructor @Component -public class SchedulerService { +public class RunnerPostDeadlineSchedulerService { private static final int CYCLE = 60000; diff --git a/backend/baton/src/main/java/touch/baton/common/schedule/deadline/query/repository/RunnerPostDeadlineQueryRepository.java b/backend/baton/src/main/java/touch/baton/common/schedule/deadline/query/repository/RunnerPostDeadlineQueryRepository.java new file mode 100644 index 000000000..2a22ac0e1 --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/common/schedule/deadline/query/repository/RunnerPostDeadlineQueryRepository.java @@ -0,0 +1,30 @@ +package touch.baton.common.schedule.deadline.query.repository; + +import jakarta.persistence.LockModeType; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Lock; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import touch.baton.domain.runnerpost.command.RunnerPost; + +import java.util.Optional; + +public interface RunnerPostDeadlineQueryRepository extends JpaRepository { + + @Query(""" + select rp + from RunnerPost rp + join fetch rp.supporter + where rp.id = :id + """) + Optional joinSupporterByRunnerPostId(@Param("id") Long runnerPostId); + + @Lock(LockModeType.PESSIMISTIC_WRITE) + @Query(""" + select rp + from RunnerPost rp + join fetch rp.supporter + where rp.id = :id + """) + Optional joinSupporterByRunnerPostIdWithLock(@Param("id") Long runnerPostId); +} diff --git a/backend/baton/src/main/java/touch/baton/config/AsyncConfig.java b/backend/baton/src/main/java/touch/baton/config/AsyncConfig.java new file mode 100644 index 000000000..8e1592054 --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/config/AsyncConfig.java @@ -0,0 +1,9 @@ +package touch.baton.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.Async; + +@Configuration +@Async +public class AsyncConfig { +} diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/RunnerPost.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/RunnerPost.java index 86e2d2721..6999f9472 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/RunnerPost.java +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/RunnerPost.java @@ -285,6 +285,10 @@ public boolean isReviewStatusNotStarted() { return reviewStatus.isNotStarted(); } + public boolean isReviewStatusDone() { + return reviewStatus.isSame(ReviewStatus.DONE); + } + @Override public boolean equals(final Object o) { if (this == o) return true; diff --git a/backend/baton/src/test/java/touch/baton/common/fixture/DeadlineOutboxFixture.java b/backend/baton/src/test/java/touch/baton/common/fixture/DeadlineOutboxFixture.java new file mode 100644 index 000000000..bb20c4732 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/common/fixture/DeadlineOutboxFixture.java @@ -0,0 +1,18 @@ +package touch.baton.common.fixture; + +import touch.baton.common.schedule.deadline.command.DeadlineOutbox; + +import java.time.Instant; + +public abstract class DeadlineOutboxFixture { + + private DeadlineOutboxFixture() { + } + + public static DeadlineOutbox deadlineOutbox(final Long runnerPostId, final Instant instantToRun) { + return DeadlineOutbox.builder() + .runnerPostId(runnerPostId) + .instantToRun(instantToRun) + .build(); + } +} diff --git a/backend/baton/src/test/java/touch/baton/common/schedule/ScheduleRunnerPostQueryRepositoryTest.java b/backend/baton/src/test/java/touch/baton/common/schedule/deadline/command/repository/ScheduleRunnerPostCommandRepositoryTest.java similarity index 82% rename from backend/baton/src/test/java/touch/baton/common/schedule/ScheduleRunnerPostQueryRepositoryTest.java rename to backend/baton/src/test/java/touch/baton/common/schedule/deadline/command/repository/ScheduleRunnerPostCommandRepositoryTest.java index 7c9d3fc0a..3526708dd 100644 --- a/backend/baton/src/test/java/touch/baton/common/schedule/ScheduleRunnerPostQueryRepositoryTest.java +++ b/backend/baton/src/test/java/touch/baton/common/schedule/deadline/command/repository/ScheduleRunnerPostCommandRepositoryTest.java @@ -1,4 +1,4 @@ -package touch.baton.common.schedule; +package touch.baton.common.schedule.deadline.command.repository; import jakarta.persistence.EntityManager; import org.junit.jupiter.api.BeforeEach; @@ -20,13 +20,15 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; -import static touch.baton.domain.runnerpost.command.vo.ReviewStatus.*; +import static touch.baton.domain.runnerpost.command.vo.ReviewStatus.DONE; +import static touch.baton.domain.runnerpost.command.vo.ReviewStatus.NOT_STARTED; +import static touch.baton.domain.runnerpost.command.vo.ReviewStatus.OVERDUE; import static touch.baton.fixture.vo.DeadlineFixture.deadline; -class ScheduleRunnerPostQueryRepositoryTest extends RepositoryTestConfig { +class ScheduleRunnerPostCommandRepositoryTest extends RepositoryTestConfig { @Autowired - private ScheduleRunnerPostRepository scheduleRunnerPostRepository; + private RunnerPostDeadlineCommandRepository runnerPostDeadlineCommandRepository; @Autowired private EntityManager em; @@ -53,8 +55,8 @@ void updateAllPassedDeadline_success() { em.persist(runnerPostTwo); // when - scheduleRunnerPostRepository.updateAllPassedDeadline(); - final List actual = scheduleRunnerPostRepository.findAll().stream() + runnerPostDeadlineCommandRepository.updateAllPassedDeadline(); + final List actual = runnerPostDeadlineCommandRepository.findAll().stream() .map(RunnerPost::getReviewStatus) .toList(); @@ -75,8 +77,8 @@ void updateAllPassedDeadline_fail_when_reviewStatus_is_DONE() { em.persist(runnerPostTwo); // when - scheduleRunnerPostRepository.updateAllPassedDeadline(); - final List actual = scheduleRunnerPostRepository.findAll().stream() + runnerPostDeadlineCommandRepository.updateAllPassedDeadline(); + final List actual = runnerPostDeadlineCommandRepository.findAll().stream() .map(RunnerPost::getReviewStatus) .toList(); @@ -97,8 +99,8 @@ void updateAllPassedDeadline_fail_when_deadline_is_not_passed() { em.persist(runnerPostTwo); // when - scheduleRunnerPostRepository.updateAllPassedDeadline(); - final List actual = scheduleRunnerPostRepository.findAll().stream() + runnerPostDeadlineCommandRepository.updateAllPassedDeadline(); + final List actual = runnerPostDeadlineCommandRepository.findAll().stream() .map(RunnerPost::getReviewStatus) .toList(); From 547541226437a896e595f55fbb25793713c5bbb5 Mon Sep 17 00:00:00 2001 From: Ethan Date: Tue, 20 Feb 2024 11:06:54 +0900 Subject: [PATCH 2/6] =?UTF-8?q?test:=20DeadlineSheduler=20Test=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RunnerPostDeadlineCheckSchedulerTest.java | 58 --------- .../deadline/command/DeadlineOutboxTest.java | 49 ++++++++ .../DeadlineOutboxCommandRepositoryTest.java | 31 +++++ .../RunnerPostDeadlineCheckSchedulerTest.java | 113 ++++++++++++++++++ ...RunnerPostDeadlineQueryRepositoryTest.java | 58 +++++++++ .../baton/config/RepositoryTestConfig.java | 5 + 6 files changed, 256 insertions(+), 58 deletions(-) delete mode 100644 backend/baton/src/test/java/touch/baton/common/schedule/RunnerPostDeadlineCheckSchedulerTest.java create mode 100644 backend/baton/src/test/java/touch/baton/common/schedule/deadline/command/DeadlineOutboxTest.java create mode 100644 backend/baton/src/test/java/touch/baton/common/schedule/deadline/command/repository/DeadlineOutboxCommandRepositoryTest.java create mode 100644 backend/baton/src/test/java/touch/baton/common/schedule/deadline/command/service/RunnerPostDeadlineCheckSchedulerTest.java create mode 100644 backend/baton/src/test/java/touch/baton/common/schedule/deadline/query/repository/RunnerPostDeadlineQueryRepositoryTest.java diff --git a/backend/baton/src/test/java/touch/baton/common/schedule/RunnerPostDeadlineCheckSchedulerTest.java b/backend/baton/src/test/java/touch/baton/common/schedule/RunnerPostDeadlineCheckSchedulerTest.java deleted file mode 100644 index 546ea6272..000000000 --- a/backend/baton/src/test/java/touch/baton/common/schedule/RunnerPostDeadlineCheckSchedulerTest.java +++ /dev/null @@ -1,58 +0,0 @@ -package touch.baton.common.schedule; - -import jakarta.persistence.EntityManager; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import touch.baton.config.ServiceTestConfig; -import touch.baton.domain.member.command.Member; -import touch.baton.domain.member.command.Runner; -import touch.baton.domain.runnerpost.command.RunnerPost; -import touch.baton.domain.runnerpost.command.vo.Deadline; -import touch.baton.fixture.domain.MemberFixture; -import touch.baton.fixture.domain.RunnerFixture; -import touch.baton.fixture.domain.RunnerPostFixture; - -import java.time.LocalDateTime; - -import static org.assertj.core.api.Assertions.assertThat; -import static touch.baton.domain.runnerpost.command.vo.ReviewStatus.OVERDUE; -import static touch.baton.fixture.vo.DeadlineFixture.deadline; - -class RunnerPostDeadlineCheckSchedulerTest extends ServiceTestConfig { - - @Autowired - private ScheduleRunnerPostRepository scheduleRunnerPostRepository; - - private RunnerPostDeadlineCheckScheduler runnerPostDeadlineCheckScheduler; - - @BeforeEach - void setUp() { - runnerPostDeadlineCheckScheduler = new RunnerPostDeadlineCheckScheduler(scheduleRunnerPostRepository); - } - - @Autowired - private EntityManager em; - - @DisplayName("1분 전에 deadline 이 지난 runnerPost 는 OVERDUE 된다.") - @Test - void runnerPost_which_passed_deadline_might_be_overdue() { - // given - final Member member = MemberFixture.createEthan(); - em.persist(member); - final Runner runner = RunnerFixture.createRunner(member); - em.persist(runner); - final Deadline passedDeadline = deadline(LocalDateTime.now().minusMinutes(1)); - final RunnerPost runnerPost = RunnerPostFixture.create(runner, passedDeadline); - em.persist(runnerPost); - - // when - runnerPostDeadlineCheckScheduler.updateReviewStatus(); - final RunnerPost actual = em.createQuery("select rp from RunnerPost rp", RunnerPost.class) - .getSingleResult(); - - // then - assertThat(actual.getReviewStatus()).isEqualTo(OVERDUE); - } -} diff --git a/backend/baton/src/test/java/touch/baton/common/schedule/deadline/command/DeadlineOutboxTest.java b/backend/baton/src/test/java/touch/baton/common/schedule/deadline/command/DeadlineOutboxTest.java new file mode 100644 index 000000000..64218b54a --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/common/schedule/deadline/command/DeadlineOutboxTest.java @@ -0,0 +1,49 @@ +package touch.baton.common.schedule.deadline.command; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import touch.baton.common.schedule.deadline.command.exception.DeadlineOutboxException; + +import java.time.Instant; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class DeadlineOutboxTest { + + @DisplayName("생성 테스트") + @Nested + class Create { + + @DisplayName("성공한다") + @Test + void success() { + assertThatCode(() -> DeadlineOutbox.builder() + .runnerPostId(1L) + .instantToRun(Instant.now()) + .build() + ).doesNotThrowAnyException(); + } + + @DisplayName("runnerPostId가 null이면 실패한다") + @Test + void fail_runnerPostId_is_null() { + assertThatThrownBy(() -> DeadlineOutbox.builder() + .runnerPostId(null) + .instantToRun(Instant.now()) + .build() + ).isInstanceOf(DeadlineOutboxException.class); + } + + @DisplayName("instantToRun이 null이면 실패한다") + @Test + void fail_instantToRun_is_null() { + assertThatThrownBy(() -> DeadlineOutbox.builder() + .runnerPostId(1L) + .instantToRun(null) + .build() + ).isInstanceOf(DeadlineOutboxException.class); + } + } +} diff --git a/backend/baton/src/test/java/touch/baton/common/schedule/deadline/command/repository/DeadlineOutboxCommandRepositoryTest.java b/backend/baton/src/test/java/touch/baton/common/schedule/deadline/command/repository/DeadlineOutboxCommandRepositoryTest.java new file mode 100644 index 000000000..4ed214e12 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/common/schedule/deadline/command/repository/DeadlineOutboxCommandRepositoryTest.java @@ -0,0 +1,31 @@ +package touch.baton.common.schedule.deadline.command.repository; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import touch.baton.common.fixture.DeadlineOutboxFixture; +import touch.baton.config.RepositoryTestConfig; + +import java.time.Instant; + +import static org.assertj.core.api.Assertions.assertThat; + +class DeadlineOutboxCommandRepositoryTest extends RepositoryTestConfig { + + @Autowired + private DeadlineOutboxCommandRepository deadlineOutboxCommandRepository; + + @DisplayName("runnerPostId로 DeadlineOutbox를 삭제할 수 있다.") + @Test + void deleteByRunnerPostId() { + // given + final long runnerPostId = 1L; + deadlineOutboxCommandRepository.save(DeadlineOutboxFixture.deadlineOutbox(runnerPostId, Instant.now())); + + // when + deadlineOutboxCommandRepository.deleteByRunnerPostId(runnerPostId); + + // then + assertThat(deadlineOutboxCommandRepository.findAll()).isEmpty(); + } +} diff --git a/backend/baton/src/test/java/touch/baton/common/schedule/deadline/command/service/RunnerPostDeadlineCheckSchedulerTest.java b/backend/baton/src/test/java/touch/baton/common/schedule/deadline/command/service/RunnerPostDeadlineCheckSchedulerTest.java new file mode 100644 index 000000000..1e860a9b0 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/common/schedule/deadline/command/service/RunnerPostDeadlineCheckSchedulerTest.java @@ -0,0 +1,113 @@ +package touch.baton.common.schedule.deadline.command.service; + +import jakarta.persistence.EntityManager; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import touch.baton.common.fixture.DeadlineOutboxFixture; +import touch.baton.common.schedule.deadline.command.repository.DeadlineOutboxCommandRepository; +import touch.baton.common.schedule.deadline.command.repository.RunnerPostDeadlineCommandRepository; +import touch.baton.common.schedule.deadline.query.repository.RunnerPostDeadlineQueryRepository; +import touch.baton.config.ServiceTestConfig; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.command.vo.ReviewCount; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.vo.Deadline; +import touch.baton.fixture.domain.MemberFixture; +import touch.baton.fixture.domain.RunnerFixture; +import touch.baton.fixture.domain.RunnerPostFixture; +import touch.baton.fixture.vo.ReviewCountFixture; + +import java.time.Instant; +import java.time.LocalDateTime; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static touch.baton.domain.runnerpost.command.vo.ReviewStatus.DONE; +import static touch.baton.domain.runnerpost.command.vo.ReviewStatus.OVERDUE; +import static touch.baton.fixture.vo.DeadlineFixture.deadline; + +class RunnerPostDeadlineCheckSchedulerTest extends ServiceTestConfig { + + @Autowired + private RunnerPostDeadlineCommandRepository runnerPostDeadlineCommandRepository; + + @Autowired + private RunnerPostDeadlineQueryRepository runnerPostDeadlineQueryRepository; + + @Autowired + private DeadlineOutboxCommandRepository deadlineOutboxCommandRepository; + + private RunnerPostDeadlineCheckScheduler runnerPostDeadlineCheckScheduler; + + @BeforeEach + void setUp() { + runnerPostDeadlineCheckScheduler = new RunnerPostDeadlineCheckScheduler(runnerPostDeadlineCommandRepository, runnerPostDeadlineQueryRepository, deadlineOutboxCommandRepository); + } + + @Autowired + private EntityManager em; + + @DisplayName("1분 전에 deadline 이 지난 runnerPost 는 OVERDUE 된다.") + @Test + void runnerPost_which_passed_deadline_might_be_overdue() { + // given + final Member member = MemberFixture.createEthan(); + em.persist(member); + final Runner runner = RunnerFixture.createRunner(member); + em.persist(runner); + final Deadline passedDeadline = deadline(LocalDateTime.now().minusMinutes(1)); + final RunnerPost runnerPost = RunnerPostFixture.create(runner, passedDeadline); + em.persist(runnerPost); + + // when + runnerPostDeadlineCheckScheduler.updateReviewStatus(); + final RunnerPost actual = em.createQuery("select rp from RunnerPost rp", RunnerPost.class) + .getSingleResult(); + + // then + assertThat(actual.getReviewStatus()).isEqualTo(OVERDUE); + } + + @DisplayName("RunnerPost를 조회할 때 Supporter를 join해서 조회한다.") + @Test + void joinBySupporter() { + // given + final Supporter supporter = persistSupporter(MemberFixture.createEthan()); + final Runner runner = persistRunner(MemberFixture.createHyena()); + final RunnerPost runnerPost = persistRunnerPost(RunnerPostFixture.create(runner, supporter)); + + // when + final RunnerPost actual = runnerPostDeadlineCheckScheduler.joinBySupporter(runnerPost.getId()); + + // then + assertThat(actual).isEqualTo(runnerPost); + } + + @DisplayName("finishReview를 호출하면 RunnerPost의 reviewStatus가 DONE으로 변경되고, DeadlineOutbox가 삭제된다.") + @Test + void finishReview() { + // given + final Runner runner = persistRunner(MemberFixture.createHyena()); + final RunnerPost runnerPost = persistRunnerPost(runner); + final Supporter supporter = persistSupporter(MemberFixture.createEthan()); + persistAssignSupporter(supporter, runnerPost); + + deadlineOutboxCommandRepository.save(DeadlineOutboxFixture.deadlineOutbox(runnerPost.getId(), Instant.now())); + + final ReviewCount originReviewCount = ReviewCountFixture.reviewCount(supporter.getReviewCount().getValue()); + + // when + runnerPostDeadlineCheckScheduler.finishReview(runnerPost.getId()); + + // then + assertAll( + () -> assertThat(runnerPost.getSupporter().getReviewCount()).isEqualTo(ReviewCountFixture.reviewCount(originReviewCount.getValue() + 1)), + () -> assertThat(runnerPost.getReviewStatus()).isEqualTo(DONE), + () -> assertThat(deadlineOutboxCommandRepository.findAll()).isEmpty() + ); + } +} diff --git a/backend/baton/src/test/java/touch/baton/common/schedule/deadline/query/repository/RunnerPostDeadlineQueryRepositoryTest.java b/backend/baton/src/test/java/touch/baton/common/schedule/deadline/query/repository/RunnerPostDeadlineQueryRepositoryTest.java new file mode 100644 index 000000000..4ff86caac --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/common/schedule/deadline/query/repository/RunnerPostDeadlineQueryRepositoryTest.java @@ -0,0 +1,58 @@ +package touch.baton.common.schedule.deadline.query.repository; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import touch.baton.config.RepositoryTestConfig; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.fixture.domain.MemberFixture; +import touch.baton.fixture.domain.RunnerPostFixture; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +class RunnerPostDeadlineQueryRepositoryTest extends RepositoryTestConfig { + + @Autowired + private RunnerPostDeadlineQueryRepository runnerPostDeadlineQueryRepository; + + @DisplayName("RunnerPost를 조회할 때 Supporter를 join해서 조회한다.") + @Test + void joinSupporterByRunnerPostId() { + // given + final Supporter supporter = persistSupporter(MemberFixture.createEthan()); + final Runner runner = persistRunner(MemberFixture.createHyena()); + final RunnerPost runnerPost = persistRunnerPost(RunnerPostFixture.create(runner, supporter)); + + // when + final Optional maybeRunnerPost = runnerPostDeadlineQueryRepository.joinSupporterByRunnerPostId(runnerPost.getId()); + + // then + assertAll( + () -> assertThat(maybeRunnerPost).isPresent(), + () -> assertThat(maybeRunnerPost).contains(runnerPost) + ); + } + + @DisplayName("Pessmistic Lock을 사용해서 RunnerPost를 조회할 때 Supporter를 join해서 조회한다.") + @Test + void joinSupporterByRunnerPostIdWithLock() { + // given + final Supporter supporter = persistSupporter(MemberFixture.createEthan()); + final Runner runner = persistRunner(MemberFixture.createHyena()); + final RunnerPost runnerPost = persistRunnerPost(RunnerPostFixture.create(runner, supporter)); + + // when + final Optional maybeRunnerPost = runnerPostDeadlineQueryRepository.joinSupporterByRunnerPostIdWithLock(runnerPost.getId()); + + // then + assertAll( + () -> assertThat(maybeRunnerPost).isPresent(), + () -> assertThat(maybeRunnerPost).contains(runnerPost) + ); + } +} diff --git a/backend/baton/src/test/java/touch/baton/config/RepositoryTestConfig.java b/backend/baton/src/test/java/touch/baton/config/RepositoryTestConfig.java index acf8a239d..b69134c71 100644 --- a/backend/baton/src/test/java/touch/baton/config/RepositoryTestConfig.java +++ b/backend/baton/src/test/java/touch/baton/config/RepositoryTestConfig.java @@ -75,6 +75,11 @@ protected RunnerPost persistRunnerPost(final Runner runner) { return runnerPost; } + protected RunnerPost persistRunnerPost(final RunnerPost runnerPost) { + em.persist(runnerPost); + return runnerPost; + } + protected SupporterRunnerPost persistApplicant(final Supporter supporter, final RunnerPost runnerPost) { final SupporterRunnerPost applicant = SupporterRunnerPostFixture.create(runnerPost, supporter); em.persist(applicant); From 6879cd82e0903e6b1f3464cfaa173fd133e65ef3 Mon Sep 17 00:00:00 2001 From: Ethan Date: Tue, 20 Feb 2024 14:10:23 +0900 Subject: [PATCH 3/6] =?UTF-8?q?refactor:=20=EB=B9=84=EB=8F=99=EA=B8=B0=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/RunnerPostDeadlineCheckScheduler.java | 6 ++---- .../src/main/java/touch/baton/config/AsyncConfig.java | 9 --------- 2 files changed, 2 insertions(+), 13 deletions(-) delete mode 100644 backend/baton/src/main/java/touch/baton/config/AsyncConfig.java diff --git a/backend/baton/src/main/java/touch/baton/common/schedule/deadline/command/service/RunnerPostDeadlineCheckScheduler.java b/backend/baton/src/main/java/touch/baton/common/schedule/deadline/command/service/RunnerPostDeadlineCheckScheduler.java index c71509aba..881805d7a 100644 --- a/backend/baton/src/main/java/touch/baton/common/schedule/deadline/command/service/RunnerPostDeadlineCheckScheduler.java +++ b/backend/baton/src/main/java/touch/baton/common/schedule/deadline/command/service/RunnerPostDeadlineCheckScheduler.java @@ -1,12 +1,11 @@ package touch.baton.common.schedule.deadline.command.service; import lombok.RequiredArgsConstructor; -import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; -import touch.baton.common.schedule.deadline.command.repository.RunnerPostDeadlineCommandRepository; -import touch.baton.common.schedule.deadline.command.repository.DeadlineOutboxCommandRepository; import touch.baton.common.schedule.deadline.command.exception.ScheduleBusinessException; +import touch.baton.common.schedule.deadline.command.repository.DeadlineOutboxCommandRepository; +import touch.baton.common.schedule.deadline.command.repository.RunnerPostDeadlineCommandRepository; import touch.baton.common.schedule.deadline.query.repository.RunnerPostDeadlineQueryRepository; import touch.baton.domain.runnerpost.command.RunnerPost; @@ -29,7 +28,6 @@ public RunnerPost joinBySupporter(final Long runnerPostId) { .orElseThrow(() -> new ScheduleBusinessException(String.format("RunnerPost를 찾을 수 없습니다. runnerPostId=%s",runnerPostId))); } - @Async public void finishReview(final Long runnerPostId) { final RunnerPost findRunnerPost = runnerPostDeadlineQueryRepository.joinSupporterByRunnerPostIdWithLock(runnerPostId) .orElseThrow(() -> new ScheduleBusinessException(String.format("RunnerPost를 찾을 수 없습니다. runnerPostId=%s",runnerPostId))); diff --git a/backend/baton/src/main/java/touch/baton/config/AsyncConfig.java b/backend/baton/src/main/java/touch/baton/config/AsyncConfig.java deleted file mode 100644 index 8e1592054..000000000 --- a/backend/baton/src/main/java/touch/baton/config/AsyncConfig.java +++ /dev/null @@ -1,9 +0,0 @@ -package touch.baton.config; - -import org.springframework.context.annotation.Configuration; -import org.springframework.scheduling.annotation.Async; - -@Configuration -@Async -public class AsyncConfig { -} From fae0edb318cfe67dce00a7bc7d6c8149bab6716f Mon Sep 17 00:00:00 2001 From: Ethan Date: Mon, 26 Feb 2024 23:47:13 +0900 Subject: [PATCH 4/6] =?UTF-8?q?test:=20=EB=8F=99=EC=8B=9C=EC=84=B1=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/RunnerPostQueryRepository.java | 4 +- .../RunnerPostDeadlineCheckSchedulerTest.java | 68 +++++++++++++++++-- .../baton/config/RepositoryTestConfig.java | 1 + 3 files changed, 65 insertions(+), 8 deletions(-) diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/query/repository/RunnerPostQueryRepository.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/query/repository/RunnerPostQueryRepository.java index 962577ca5..f83c710b4 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/query/repository/RunnerPostQueryRepository.java +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/query/repository/RunnerPostQueryRepository.java @@ -24,8 +24,8 @@ public interface RunnerPostQueryRepository extends JpaRepository joinSupporterByRunnerPostId(@Param("runnerPostId") final Long runnerPostId); diff --git a/backend/baton/src/test/java/touch/baton/common/schedule/deadline/command/service/RunnerPostDeadlineCheckSchedulerTest.java b/backend/baton/src/test/java/touch/baton/common/schedule/deadline/command/service/RunnerPostDeadlineCheckSchedulerTest.java index 1e860a9b0..06986aac4 100644 --- a/backend/baton/src/test/java/touch/baton/common/schedule/deadline/command/service/RunnerPostDeadlineCheckSchedulerTest.java +++ b/backend/baton/src/test/java/touch/baton/common/schedule/deadline/command/service/RunnerPostDeadlineCheckSchedulerTest.java @@ -1,10 +1,13 @@ package touch.baton.common.schedule.deadline.command.service; -import jakarta.persistence.EntityManager; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.transaction.TestTransaction; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.support.TransactionTemplate; import touch.baton.common.fixture.DeadlineOutboxFixture; import touch.baton.common.schedule.deadline.command.repository.DeadlineOutboxCommandRepository; import touch.baton.common.schedule.deadline.command.repository.RunnerPostDeadlineCommandRepository; @@ -23,6 +26,9 @@ import java.time.Instant; import java.time.LocalDateTime; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; @@ -41,16 +47,20 @@ class RunnerPostDeadlineCheckSchedulerTest extends ServiceTestConfig { @Autowired private DeadlineOutboxCommandRepository deadlineOutboxCommandRepository; + @Autowired + private PlatformTransactionManager platformTransactionManager; + private RunnerPostDeadlineCheckScheduler runnerPostDeadlineCheckScheduler; + private TransactionTemplate transactionTemplate; + @BeforeEach void setUp() { + transactionTemplate = new TransactionTemplate(platformTransactionManager); + transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); runnerPostDeadlineCheckScheduler = new RunnerPostDeadlineCheckScheduler(runnerPostDeadlineCommandRepository, runnerPostDeadlineQueryRepository, deadlineOutboxCommandRepository); } - @Autowired - private EntityManager em; - @DisplayName("1분 전에 deadline 이 지난 runnerPost 는 OVERDUE 된다.") @Test void runnerPost_which_passed_deadline_might_be_overdue() { @@ -104,10 +114,56 @@ void finishReview() { runnerPostDeadlineCheckScheduler.finishReview(runnerPost.getId()); // then + assertAll(() -> assertThat(runnerPost.getSupporter().getReviewCount()).isEqualTo(ReviewCountFixture.reviewCount(originReviewCount.getValue() + 1)), () -> assertThat(runnerPost.getReviewStatus()).isEqualTo(DONE), () -> assertThat(deadlineOutboxCommandRepository.findAll()).isEmpty()); + } + + @DisplayName("다수의 스케줄이 동시에 실행되도 ReviewCount는 1개만 증가한다.") + @Test + void finishReview_when_multiple_request() throws InterruptedException { + // given + final Runner runner = persistRunner(MemberFixture.createHyena()); + final RunnerPost runnerPost = persistRunnerPost(runner); + final Supporter supporter = persistSupporter(MemberFixture.createEthan()); + persistAssignSupporter(supporter, runnerPost); + deadlineOutboxCommandRepository.save(DeadlineOutboxFixture.deadlineOutbox(runnerPost.getId(), Instant.now())); + + transactionCommit(); + + final ReviewCount originReviewCount = ReviewCountFixture.reviewCount(supporter.getReviewCount().getValue()); + final int requests = 10; + final ExecutorService executorService = Executors.newFixedThreadPool(requests); + final CountDownLatch latch = new CountDownLatch(requests); + + // when + for (int i = 0; i < requests; i++) { + executorService.submit(() -> + transactionTemplate.execute((none) -> { + try { + runnerPostDeadlineCheckScheduler.finishReview(runnerPost.getId()); + } catch (final Exception e) { + e.printStackTrace(); + } finally { + latch.countDown(); + } + return null; + }) + ); + } + + latch.await(); + executorService.shutdown(); + + // then + final RunnerPost actual = runnerPostQueryRepository.joinSupporterByRunnerPostId(runnerPost.getId()).get(); assertAll( - () -> assertThat(runnerPost.getSupporter().getReviewCount()).isEqualTo(ReviewCountFixture.reviewCount(originReviewCount.getValue() + 1)), - () -> assertThat(runnerPost.getReviewStatus()).isEqualTo(DONE), + () -> assertThat(actual.getSupporter().getReviewCount()).isEqualTo(ReviewCountFixture.reviewCount(originReviewCount.getValue() + 1)), + () -> assertThat(actual.isReviewStatusDone()).isTrue(), () -> assertThat(deadlineOutboxCommandRepository.findAll()).isEmpty() ); } + + private void transactionCommit() { + TestTransaction.flagForCommit(); + TestTransaction.end(); + } } diff --git a/backend/baton/src/test/java/touch/baton/config/RepositoryTestConfig.java b/backend/baton/src/test/java/touch/baton/config/RepositoryTestConfig.java index b69134c71..a9ab5db5d 100644 --- a/backend/baton/src/test/java/touch/baton/config/RepositoryTestConfig.java +++ b/backend/baton/src/test/java/touch/baton/config/RepositoryTestConfig.java @@ -89,6 +89,7 @@ protected SupporterRunnerPost persistApplicant(final Supporter supporter, final protected void persistAssignSupporter(final Supporter supporter, final RunnerPost runnerPost) { runnerPost.assignSupporter(supporter); em.persist(runnerPost); + em.flush(); } protected Tag persistTag(final String tagName) { From f5c1a7a07cf04b5bb3f5bd0c7260cf830c78bca9 Mon Sep 17 00:00:00 2001 From: Ethan Date: Tue, 27 Feb 2024 00:20:12 +0900 Subject: [PATCH 5/6] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=A0=95=ED=95=A9=EC=84=B1=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/RunnerPostDeadlineCheckSchedulerTest.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/backend/baton/src/test/java/touch/baton/common/schedule/deadline/command/service/RunnerPostDeadlineCheckSchedulerTest.java b/backend/baton/src/test/java/touch/baton/common/schedule/deadline/command/service/RunnerPostDeadlineCheckSchedulerTest.java index 06986aac4..4a9cacf09 100644 --- a/backend/baton/src/test/java/touch/baton/common/schedule/deadline/command/service/RunnerPostDeadlineCheckSchedulerTest.java +++ b/backend/baton/src/test/java/touch/baton/common/schedule/deadline/command/service/RunnerPostDeadlineCheckSchedulerTest.java @@ -1,5 +1,6 @@ package touch.baton.common.schedule.deadline.command.service; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -61,6 +62,14 @@ void setUp() { runnerPostDeadlineCheckScheduler = new RunnerPostDeadlineCheckScheduler(runnerPostDeadlineCommandRepository, runnerPostDeadlineQueryRepository, deadlineOutboxCommandRepository); } + @AfterEach + void tearDown() { + runnerPostCommandRepository.deleteAll(); + supporterCommandRepository.deleteAll(); + runnerQueryRepository.deleteAll(); + memberCommandRepository.deleteAll(); + } + @DisplayName("1분 전에 deadline 이 지난 runnerPost 는 OVERDUE 된다.") @Test void runnerPost_which_passed_deadline_might_be_overdue() { @@ -141,7 +150,6 @@ void finishReview_when_multiple_request() throws InterruptedException { try { runnerPostDeadlineCheckScheduler.finishReview(runnerPost.getId()); } catch (final Exception e) { - e.printStackTrace(); } finally { latch.countDown(); } From d9c81f552726e609c9e8eb7ce88cdc1eb53a23c2 Mon Sep 17 00:00:00 2001 From: Ethan Date: Tue, 27 Feb 2024 00:38:02 +0900 Subject: [PATCH 6/6] =?UTF-8?q?test:=20=EC=A4=91=EB=B3=B5=20=EC=A0=95?= =?UTF-8?q?=EC=9D=98=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test/java/touch/baton/config/RepositoryTestConfig.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/backend/baton/src/test/java/touch/baton/config/RepositoryTestConfig.java b/backend/baton/src/test/java/touch/baton/config/RepositoryTestConfig.java index b969ffbdb..d18d1c976 100644 --- a/backend/baton/src/test/java/touch/baton/config/RepositoryTestConfig.java +++ b/backend/baton/src/test/java/touch/baton/config/RepositoryTestConfig.java @@ -80,11 +80,6 @@ protected RunnerPost persistRunnerPost(final Runner runner) { return runnerPost; } - protected RunnerPost persistRunnerPost(final RunnerPost runnerPost) { - em.persist(runnerPost); - return runnerPost; - } - protected SupporterRunnerPost persistApplicant(final Supporter supporter, final RunnerPost runnerPost) { final SupporterRunnerPost applicant = SupporterRunnerPostFixture.create(runnerPost, supporter); em.persist(applicant);