From 66866c9a6c08c255a4f7a4b12be7c52c108e1c84 Mon Sep 17 00:00:00 2001 From: Seonghun Jeong <119427233+zzoe2346@users.noreply.github.com> Date: Tue, 15 Oct 2024 08:27:57 +0900 Subject: [PATCH] =?UTF-8?q?Feat:=20=EC=BD=9C=EB=B0=B1=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=EC=A0=95=EC=B1=85=20=EC=A0=81=EC=9A=A9=20=EA=B7=B8?= =?UTF-8?q?=EB=A6=AC=EA=B3=A0=20=EC=9D=B4=EC=9A=A9=20=EB=82=B4=EC=97=AD=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20(#64)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/sinitto/SinittoApplication.java | 2 + .../controller/CallbackController.java | 17 ++- .../dto/CallbackUsageHistoryResponse.java | 11 ++ .../sinitto/callback/entity/Callback.java | 9 ++ .../MemberHasInProgressCallbackException.java | 8 ++ .../repository/CallbackRepository.java | 11 +- .../callback/service/CallbackService.java | 57 +++++++- .../guard/repository/SeniorRepository.java | 3 + .../controller/PointAdminController.java | 4 +- .../example/sinitto/point/entity/Point.java | 5 +- .../sinitto/callback/entity/CallbackTest.java | 33 ++++- .../repository/CallbackRepositoryTest.java | 125 +++++++++++++++++- .../callback/service/CallbackServiceTest.java | 84 ++++++++++-- 13 files changed, 336 insertions(+), 33 deletions(-) create mode 100644 src/main/java/com/example/sinitto/callback/dto/CallbackUsageHistoryResponse.java create mode 100644 src/main/java/com/example/sinitto/callback/exception/MemberHasInProgressCallbackException.java diff --git a/src/main/java/com/example/sinitto/SinittoApplication.java b/src/main/java/com/example/sinitto/SinittoApplication.java index 36245d70..58305608 100644 --- a/src/main/java/com/example/sinitto/SinittoApplication.java +++ b/src/main/java/com/example/sinitto/SinittoApplication.java @@ -5,10 +5,12 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication @EnableJpaAuditing @EnableConfigurationProperties(KakaoProperties.class) +@EnableScheduling public class SinittoApplication { public static void main(String[] args) { diff --git a/src/main/java/com/example/sinitto/callback/controller/CallbackController.java b/src/main/java/com/example/sinitto/callback/controller/CallbackController.java index e99cb5c2..e3aeab14 100644 --- a/src/main/java/com/example/sinitto/callback/controller/CallbackController.java +++ b/src/main/java/com/example/sinitto/callback/controller/CallbackController.java @@ -1,6 +1,7 @@ package com.example.sinitto.callback.controller; import com.example.sinitto.callback.dto.CallbackResponse; +import com.example.sinitto.callback.dto.CallbackUsageHistoryResponse; import com.example.sinitto.callback.service.CallbackService; import com.example.sinitto.common.annotation.MemberId; import io.swagger.v3.oas.annotations.Operation; @@ -25,10 +26,10 @@ public CallbackController(CallbackService callbackService) { @Operation(summary = "콜백 전화 리스트 보기(페이지)", description = "시니어가 요청한 콜백전화를 페이징으로 보여줍니다.") @GetMapping - public ResponseEntity> getCallbackList(@MemberId Long memberId, - @PageableDefault(sort = "postTime", direction = Sort.Direction.DESC) Pageable pageable) { + public ResponseEntity> getWaitingCallbackList(@MemberId Long memberId, + @PageableDefault(sort = "postTime", direction = Sort.Direction.DESC) Pageable pageable) { - return ResponseEntity.ok(callbackService.getCallbacks(memberId, pageable)); + return ResponseEntity.ok(callbackService.getWaitingCallbacks(memberId, pageable)); } @Operation(summary = "진행 상태인 콜백을 완료 대기 상태로 전환(시니또가)", description = "시니또가 수락한 콜백 수행을 완료했을때 이 api 호출하면 완료 대기 상태로 변합니다.") @@ -45,7 +46,7 @@ public ResponseEntity pendingCompleteCallback(@MemberId Long memberId, public ResponseEntity completeCallback(@MemberId Long memberId, @PathVariable Long callbackId) { - callbackService.complete(memberId, callbackId); + callbackService.changeCallbackStatusToCompleteByGuard(memberId, callbackId); return ResponseEntity.ok().build(); } @@ -79,4 +80,12 @@ public ResponseEntity getAcceptedCallback(@MemberId Long membe return ResponseEntity.ok(callbackService.getAcceptedCallback(memberId)); } + + @Operation(summary = "보호자의 콜백 이용 내역 조회", description = "보호자에게 연관된 모든 콜백 내역을 조회합니다. 기본적으로 최신 내역 부터 조회됩니다.") + @GetMapping("/guard/requested") + public ResponseEntity> getAcceptedCallback(@MemberId Long memberId, + @PageableDefault(sort = "postTime", direction = Sort.Direction.DESC) Pageable pageable) { + + return ResponseEntity.ok(callbackService.getCallbackHistoryOfGuard(memberId, pageable)); + } } diff --git a/src/main/java/com/example/sinitto/callback/dto/CallbackUsageHistoryResponse.java b/src/main/java/com/example/sinitto/callback/dto/CallbackUsageHistoryResponse.java new file mode 100644 index 00000000..a13d6509 --- /dev/null +++ b/src/main/java/com/example/sinitto/callback/dto/CallbackUsageHistoryResponse.java @@ -0,0 +1,11 @@ +package com.example.sinitto.callback.dto; + +import java.time.LocalDateTime; + +public record CallbackUsageHistoryResponse( + Long callbackId, + String seniorName, + LocalDateTime postTime, + String status +) { +} diff --git a/src/main/java/com/example/sinitto/callback/entity/Callback.java b/src/main/java/com/example/sinitto/callback/entity/Callback.java index c9425f57..89f67932 100644 --- a/src/main/java/com/example/sinitto/callback/entity/Callback.java +++ b/src/main/java/com/example/sinitto/callback/entity/Callback.java @@ -23,6 +23,7 @@ public class Callback { private Long id; @CreatedDate private LocalDateTime postTime; + private LocalDateTime pendingCompleteTime; @NotNull @Enumerated(EnumType.STRING) private Callback.Status status; @@ -140,6 +141,14 @@ public Long getAssignedMemberId() { return assignedMemberId; } + public LocalDateTime getPendingCompleteTime() { + return pendingCompleteTime; + } + + public void setPendingCompleteTime(LocalDateTime pendingCompleteTime) { + this.pendingCompleteTime = pendingCompleteTime; + } + public enum Status { WAITING, IN_PROGRESS, diff --git a/src/main/java/com/example/sinitto/callback/exception/MemberHasInProgressCallbackException.java b/src/main/java/com/example/sinitto/callback/exception/MemberHasInProgressCallbackException.java new file mode 100644 index 00000000..77f44b55 --- /dev/null +++ b/src/main/java/com/example/sinitto/callback/exception/MemberHasInProgressCallbackException.java @@ -0,0 +1,8 @@ +package com.example.sinitto.callback.exception; + +public class MemberHasInProgressCallbackException extends ConflictException { + + public MemberHasInProgressCallbackException(String message) { + super(message); + } +} diff --git a/src/main/java/com/example/sinitto/callback/repository/CallbackRepository.java b/src/main/java/com/example/sinitto/callback/repository/CallbackRepository.java index b34aa1cb..eeeb617b 100644 --- a/src/main/java/com/example/sinitto/callback/repository/CallbackRepository.java +++ b/src/main/java/com/example/sinitto/callback/repository/CallbackRepository.java @@ -1,15 +1,24 @@ package com.example.sinitto.callback.repository; import com.example.sinitto.callback.entity.Callback; +import com.example.sinitto.member.entity.Senior; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import java.time.LocalDateTime; +import java.util.List; import java.util.Optional; public interface CallbackRepository extends JpaRepository { - Page findAll(Pageable pageable); + Page findAllByStatus(Callback.Status status, Pageable pageable); Optional findByAssignedMemberIdAndStatus(Long memberId, Callback.Status status); + + boolean existsByAssignedMemberIdAndStatus(Long memberId, Callback.Status status); + + Page findAllBySeniorIn(List seniors, Pageable pageable); + + List findAllByStatusAndPendingCompleteTimeBefore(Callback.Status status, LocalDateTime dateTime); } diff --git a/src/main/java/com/example/sinitto/callback/service/CallbackService.java b/src/main/java/com/example/sinitto/callback/service/CallbackService.java index 94d61979..5fcb3216 100644 --- a/src/main/java/com/example/sinitto/callback/service/CallbackService.java +++ b/src/main/java/com/example/sinitto/callback/service/CallbackService.java @@ -1,6 +1,7 @@ package com.example.sinitto.callback.service; import com.example.sinitto.callback.dto.CallbackResponse; +import com.example.sinitto.callback.dto.CallbackUsageHistoryResponse; import com.example.sinitto.callback.entity.Callback; import com.example.sinitto.callback.exception.*; import com.example.sinitto.callback.repository.CallbackRepository; @@ -16,13 +17,18 @@ import com.example.sinitto.point.repository.PointRepository; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDateTime; +import java.util.List; + @Service public class CallbackService { - public static final int CALLBACK_PRICE = 1500; + private static final int CALLBACK_PRICE = 1500; + private static final int DAYS_FOR_AUTO_COMPLETE = 2; private static final String SUCCESS_MESSAGE = "감사합니다. 잠시만 기다려주세요."; private static final String FAIL_MESSAGE_NOT_ENROLLED = "등록된 사용자가 아닙니다. 서비스 이용이 불가합니다."; private static final String FAIL_MESSAGE_NOT_ENOUGH_POINT = "포인트가 부족합니다. 서비스 이용이 불가합니다."; @@ -41,11 +47,11 @@ public CallbackService(CallbackRepository callbackRepository, MemberRepository m } @Transactional(readOnly = true) - public Page getCallbacks(Long memberId, Pageable pageable) { + public Page getWaitingCallbacks(Long memberId, Pageable pageable) { checkAuthorization(memberId); - return callbackRepository.findAll(pageable) + return callbackRepository.findAllByStatus(Callback.Status.WAITING, pageable) .map((callback) -> new CallbackResponse(callback.getId(), callback.getSeniorName(), callback.getPostTime(), callback.getStatus(), callback.getSeniorId())); } @@ -54,6 +60,10 @@ public void accept(Long memberId, Long callbackId) { checkAuthorization(memberId); + if (callbackRepository.existsByAssignedMemberIdAndStatus(memberId, Callback.Status.IN_PROGRESS)) { + throw new MemberHasInProgressCallbackException("이 요청을 한 시니또는 이미 진행중인 콜백이 있습니다."); + } + Callback callback = getCallbackOrThrow(callbackId); callback.assignMember(memberId); @@ -70,10 +80,11 @@ public void pendingComplete(Long memberId, Long callbackId) { checkAssignment(memberId, callback.getAssignedMemberId()); callback.changeStatusToPendingComplete(); + callback.setPendingCompleteTime(LocalDateTime.now()); } @Transactional - public void complete(Long memberId, Long callbackId) { + public void changeCallbackStatusToCompleteByGuard(Long memberId, Long callbackId) { Callback callback = getCallbackOrThrow(callbackId); @@ -84,12 +95,33 @@ public void complete(Long memberId, Long callbackId) { throw new GuardMismatchException("이 API를 요청한 보호자는 이 콜백을 요청 한 시니어의 보호자가 아닙니다."); } - Point sinittoPoint = pointRepository.findByMemberId(callback.getAssignedMemberId()) + earnPointForSinitto(callback.getAssignedMemberId()); + callback.changeStatusToComplete(); + } + + private void earnPointForSinitto(Long sinittoMemberId) { + + Point sinittoPoint = pointRepository.findByMemberId(sinittoMemberId) .orElseThrow(() -> new PointNotFoundException("포인트 적립 받을 시니또와 연관된 포인트가 없습니다")); + sinittoPoint.earn(CALLBACK_PRICE); + pointLogRepository.save(new PointLog(PointLog.Content.COMPLETE_CALLBACK_AND_EARN.getMessage(), sinittoPoint.getMember(), CALLBACK_PRICE, PointLog.Status.EARN)); + } - callback.changeStatusToComplete(); + @Scheduled(cron = "0 */10 * * * *") + @Transactional + public void changeOldPendingCompleteToCompleteByPolicy() { + + LocalDateTime referenceDateTimeForComplete = LocalDateTime.now().minusDays(DAYS_FOR_AUTO_COMPLETE); + + List callbacks = callbackRepository.findAllByStatusAndPendingCompleteTimeBefore(Callback.Status.PENDING_COMPLETE, referenceDateTimeForComplete); + + for (Callback callback : callbacks) { + + earnPointForSinitto(callback.getAssignedMemberId()); + callback.changeStatusToComplete(); + } } @Transactional @@ -176,4 +208,17 @@ private void checkAssignment(Long memberId, Long assignedMemberId) { throw new NotAssignedException("이 콜백에 할당된 시니또가 아닙니다"); } } + + @Transactional(readOnly = true) + public Page getCallbackHistoryOfGuard(Long memberId, Pageable pageable) { + + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new NotMemberException("멤버가 아닙니다")); + + List seniors = seniorRepository.findAllByMember(member); + + + return callbackRepository.findAllBySeniorIn(seniors, pageable) + .map(callback -> new CallbackUsageHistoryResponse(callback.getId(), callback.getSeniorName(), callback.getPostTime(), callback.getStatus())); + } } diff --git a/src/main/java/com/example/sinitto/guard/repository/SeniorRepository.java b/src/main/java/com/example/sinitto/guard/repository/SeniorRepository.java index 0f60727a..71309097 100644 --- a/src/main/java/com/example/sinitto/guard/repository/SeniorRepository.java +++ b/src/main/java/com/example/sinitto/guard/repository/SeniorRepository.java @@ -1,5 +1,6 @@ package com.example.sinitto.guard.repository; +import com.example.sinitto.member.entity.Member; import com.example.sinitto.member.entity.Senior; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -14,4 +15,6 @@ public interface SeniorRepository extends JpaRepository { Optional findByIdAndMemberId(Long Id, Long memberId); Optional findByPhoneNumber(String phoneNumber); + + List findAllByMember(Member member); } diff --git a/src/main/java/com/example/sinitto/point/controller/PointAdminController.java b/src/main/java/com/example/sinitto/point/controller/PointAdminController.java index bffa0f38..21eea31d 100644 --- a/src/main/java/com/example/sinitto/point/controller/PointAdminController.java +++ b/src/main/java/com/example/sinitto/point/controller/PointAdminController.java @@ -54,7 +54,7 @@ public String showAllChargeRequest(Model model) { } model.addAttribute("logWithDepositMessages", logWithDepositMessages); - return "/point/charge"; + return "point/charge"; } @PostMapping("/admin/point/charge/waiting/{pointLogId}") @@ -101,7 +101,7 @@ public String showAllWithdrawRequest(Model model) { } model.addAttribute("logWithBankInfos", logWithBankInfos); - return "/point/withdraw"; + return "point/withdraw"; } @PostMapping("/admin/point/withdraw/waiting/{pointLogId}") diff --git a/src/main/java/com/example/sinitto/point/entity/Point.java b/src/main/java/com/example/sinitto/point/entity/Point.java index 74612611..5eab55b1 100644 --- a/src/main/java/com/example/sinitto/point/entity/Point.java +++ b/src/main/java/com/example/sinitto/point/entity/Point.java @@ -54,9 +54,6 @@ public void deduct(int priceToDeduct) { public boolean isSufficientForDeduction(int priceToDeduct) { - if (this.price < priceToDeduct) { - return false; - } - return true; + return this.price >= priceToDeduct; } } diff --git a/src/test/java/com/example/sinitto/callback/entity/CallbackTest.java b/src/test/java/com/example/sinitto/callback/entity/CallbackTest.java index d2fca9be..c9879f2e 100644 --- a/src/test/java/com/example/sinitto/callback/entity/CallbackTest.java +++ b/src/test/java/com/example/sinitto/callback/entity/CallbackTest.java @@ -14,6 +14,12 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; + +import java.util.List; import static org.junit.jupiter.api.Assertions.*; @@ -27,14 +33,14 @@ class CallbackTest { @Autowired private SeniorRepository seniorRepository; private Callback testCallback; + private Member testMember; + private Senior testSenior; @BeforeEach void setUp() { - Member testMember = new Member("member", "01043214321", "tjdgns5506@gmai.com", true); - memberRepository.save(testMember); + testMember = memberRepository.save(new Member("member", "01043214321", "tjdgns5506@gmai.com", true)); - Senior testSenior = new Senior("senior", "01012341234", testMember); - seniorRepository.save(testSenior); + testSenior = seniorRepository.save(new Senior("senior", "01012341234", testMember)); testCallback = callbackRepository.save(new Callback(Callback.Status.WAITING, testSenior)); } @@ -264,4 +270,23 @@ void changeStatusToComplete_fail2() { assertThrows(AlreadyInProgressException.class, () -> testCallback.changeStatusToComplete()); } + @Test + @DisplayName("보호자에 연관된 다수의 시니어 리스트를 얻은후, 다수의 시니어들이 신청한 콜백 요청 기록 조회 테스트(CallbackService 의 getCallbackHistoryOfGuard() 테스트나 마찬가지)") + void getSeniorListAndReadCallbackHistoryOfSeniors() { + //given + Senior testSenior2 = new Senior("senior2", "01099999999", testMember); + seniorRepository.save(testSenior2); + + callbackRepository.save(new Callback(Callback.Status.WAITING, testSenior2)); + callbackRepository.save(new Callback(Callback.Status.COMPLETE, testSenior2)); + Pageable pageable = PageRequest.of(0, 1, Sort.by(Sort.Direction.DESC, "postTime")); + //when + Page result = callbackRepository.findAllBySeniorIn(List.of(testSenior, testSenior2), pageable); + + //then + assertEquals(3, result.getTotalPages()); + assertEquals(testSenior2, result.getContent().getFirst().getSenior()); + assertEquals(Callback.Status.COMPLETE.toString(), result.getContent().getFirst().getStatus()); + } + } diff --git a/src/test/java/com/example/sinitto/callback/repository/CallbackRepositoryTest.java b/src/test/java/com/example/sinitto/callback/repository/CallbackRepositoryTest.java index 8de07246..d5ae6833 100644 --- a/src/test/java/com/example/sinitto/callback/repository/CallbackRepositoryTest.java +++ b/src/test/java/com/example/sinitto/callback/repository/CallbackRepositoryTest.java @@ -11,6 +11,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import java.time.LocalDateTime; +import java.util.List; import java.util.Optional; import static org.junit.jupiter.api.Assertions.*; @@ -26,10 +28,11 @@ class CallbackRepositoryTest { private SeniorRepository seniorRepository; private Callback testCallback; private Long memberId; + private Member testMember; @BeforeEach void setUp() { - Member testMember = new Member("member", "01043214321", "tjdgns5506@gmai.com", true); + testMember = new Member("member", "01043214321", "tjdgns5506@gmai.com", true); memberRepository.save(testMember); memberId = testMember.getId(); @@ -97,4 +100,124 @@ void findByAssignedMemberId_fail3() { //then assertTrue(result.isEmpty()); } + + @Test + @DisplayName("콜백에 할당된 멤버id와 상태를 매게변수로한 조회 - 성공") + void existsByAssignedMemberIdAndStatus() { + //given + testCallback.assignMember(memberId); + testCallback.changeStatusToInProgress(); + + //when + boolean result = callbackRepository.existsByAssignedMemberIdAndStatus(memberId, Callback.Status.IN_PROGRESS); + + //then + assertTrue(result); + } + + @Test + @DisplayName("콜백에 할당된 멤버id와 상태를 매게변수로한 조회 - 실패(멤버id 아닌 경우)") + void existsByAssignedMemberIdAndStatus_fail1() { + //given + testCallback.assignMember(memberId); + testCallback.changeStatusToInProgress(); + + //when + boolean result = callbackRepository.existsByAssignedMemberIdAndStatus(memberId + 1L, Callback.Status.IN_PROGRESS); + + //then + assertFalse(result); + } + + @Test + @DisplayName("콜백에 할당된 멤버id와 상태를 매게변수로한 조회 - 실패(멤버id만 틀리는 경우)") + void existsByAssignedMemberIdAndStatus_fail2() { + //given + testCallback.assignMember(memberId); + testCallback.changeStatusToInProgress(); + + //when + boolean result = callbackRepository.existsByAssignedMemberIdAndStatus(memberId + 1L, Callback.Status.IN_PROGRESS); + + //then + assertFalse(result); + } + + @Test + @DisplayName("콜백에 할당된 멤버id와 상태를 매게변수로한 조회 - 실패(콜백 상태만 틀리는 경우)") + void existsByAssignedMemberIdAndStatus_fail3() { + //given + testCallback.assignMember(memberId); + testCallback.changeStatusToInProgress(); + + //when + boolean result = callbackRepository.existsByAssignedMemberIdAndStatus(memberId, Callback.Status.PENDING_COMPLETE); + + //then + assertFalse(result); + } + + @Test + @DisplayName("콜백에 할당된 멤버id와 상태를 매게변수로한 조회 - 실패(멤버id, 콜백 상태 모두 틀리는 경우)") + void existsByAssignedMemberIdAndStatus_fail4() { + //given + testCallback.assignMember(memberId); + testCallback.changeStatusToInProgress(); + + //when + boolean result = callbackRepository.existsByAssignedMemberIdAndStatus(memberId + 1L, Callback.Status.WAITING); + + //then + assertFalse(result); + } + + + @Test + @DisplayName("findAllByStatusAndPendingCompleteTimeBefore 이용해서 2일이 지난 PendingComplete 콜백 조회 - 성공") + void findAllByStatusAndPendingCompleteTimeBefore_success() { + // given + LocalDateTime beforeTime = LocalDateTime.of(2024, 1, 5, 13, 00).minusDays(2); + Senior testSenior = seniorRepository.save(new Senior("senior", "01012341234", testMember)); + + Callback callback1 = callbackRepository.save(new Callback(Callback.Status.PENDING_COMPLETE, testSenior)); + callback1.setPendingCompleteTime(LocalDateTime.of(2024, 1, 3, 12, 58)); + Callback callback2 = callbackRepository.save(new Callback(Callback.Status.PENDING_COMPLETE, testSenior)); + callback2.setPendingCompleteTime(LocalDateTime.of(2024, 1, 3, 12, 59)); + Callback callback3 = callbackRepository.save(new Callback(Callback.Status.PENDING_COMPLETE, testSenior)); + callback3.setPendingCompleteTime(LocalDateTime.of(2024, 1, 3, 12, 59)); + Callback callback4 = callbackRepository.save(new Callback(Callback.Status.PENDING_COMPLETE, testSenior)); + callback4.setPendingCompleteTime(LocalDateTime.of(2024, 1, 3, 13, 01)); + Callback callback5 = callbackRepository.save(new Callback(Callback.Status.PENDING_COMPLETE, testSenior)); + callback5.setPendingCompleteTime(LocalDateTime.of(2024, 1, 3, 13, 02)); + + // when + List result = callbackRepository.findAllByStatusAndPendingCompleteTimeBefore(Callback.Status.PENDING_COMPLETE, beforeTime); + + // then + assertEquals(3, result.size()); + assertTrue(result.contains(callback1)); + assertTrue(result.contains(callback2)); + assertTrue(result.contains(callback3)); + } + + @Test + @DisplayName("findAllByStatusAndPendingCompleteTimeBefore 이용해서 2일이 지난 PendingComplete 콜백 조회 - 만약 아무것도 조건에 해당 안될 경우 테스트") + void findAllByStatusAndPendingCompleteTimeBefore_success_zeroList() { + // given + LocalDateTime beforeTime = LocalDateTime.of(2024, 1, 5, 13, 00).minusDays(2); + Senior testSenior = seniorRepository.save(new Senior("senior", "01012341234", testMember)); + + Callback callback1 = callbackRepository.save(new Callback(Callback.Status.PENDING_COMPLETE, testSenior)); + callback1.setPendingCompleteTime(LocalDateTime.of(2024, 1, 3, 13, 01)); + Callback callback2 = callbackRepository.save(new Callback(Callback.Status.PENDING_COMPLETE, testSenior)); + callback2.setPendingCompleteTime(LocalDateTime.of(2024, 1, 3, 13, 02)); + + // when + List result = callbackRepository.findAllByStatusAndPendingCompleteTimeBefore(Callback.Status.PENDING_COMPLETE, beforeTime); + + // then + assertNotNull(result); + assertEquals(0, result.size()); + + } } diff --git a/src/test/java/com/example/sinitto/callback/service/CallbackServiceTest.java b/src/test/java/com/example/sinitto/callback/service/CallbackServiceTest.java index d740acee..979b6c3e 100644 --- a/src/test/java/com/example/sinitto/callback/service/CallbackServiceTest.java +++ b/src/test/java/com/example/sinitto/callback/service/CallbackServiceTest.java @@ -13,6 +13,7 @@ import com.example.sinitto.member.entity.Senior; import com.example.sinitto.member.repository.MemberRepository; import com.example.sinitto.point.entity.Point; +import com.example.sinitto.point.entity.PointLog; import com.example.sinitto.point.repository.PointLogRepository; import com.example.sinitto.point.repository.PointRepository; import org.junit.jupiter.api.DisplayName; @@ -50,7 +51,7 @@ class CallbackServiceTest { @Test @DisplayName("getCallbacks - 성공") - void getCallbacks() { + void getWaitingCallbacks() { //given Long memberId = 1L; Pageable pageable = PageRequest.of(0, 10); @@ -66,10 +67,10 @@ void getCallbacks() { when(memberRepository.findById(memberId)).thenReturn(Optional.of(member)); when(member.isSinitto()).thenReturn(true); - when(callbackRepository.findAll(pageable)).thenReturn(callbackPage); + when(callbackRepository.findAllByStatus(Callback.Status.WAITING, pageable)).thenReturn(callbackPage); //when - Page result = callbackService.getCallbacks(memberId, pageable); + Page result = callbackService.getWaitingCallbacks(memberId, pageable); //then assertEquals(1, result.getContent().size()); @@ -78,7 +79,7 @@ void getCallbacks() { @Test @DisplayName("getCallbacks 멤버가 없을때 - 실패") - void getCallbacks_Fail_WhenNotMember() { + void getWaitingCallbacks_Fail_WhenNotMember() { //given Long memberId = 1L; Pageable pageable = PageRequest.of(0, 10); @@ -86,7 +87,7 @@ void getCallbacks_Fail_WhenNotMember() { when(memberRepository.findById(memberId)).thenReturn(Optional.empty()); //when then - assertThrows(NotMemberException.class, () -> callbackService.getCallbacks(memberId, pageable)); + assertThrows(NotMemberException.class, () -> callbackService.getWaitingCallbacks(memberId, pageable)); } @Test @@ -101,7 +102,7 @@ void getCallbacks_Fail_WhenNotSinitto() { when(member.isSinitto()).thenReturn(false); //when then - assertThrows(NotSinittoException.class, () -> callbackService.getCallbacks(memberId, pageable)); + assertThrows(NotSinittoException.class, () -> callbackService.getWaitingCallbacks(memberId, pageable)); } @Test @@ -128,7 +129,7 @@ void accept() { @Test @DisplayName("콜백 완료 대기 - 성공") - void complete() { + void changeCallbackStatusToCompleteByGuard() { //given Long memberId = 1L; Long callbackId = 1L; @@ -215,7 +216,7 @@ void addCallback_fail() { @Test @DisplayName("보호자가 콜백 대기 상태를 완료 생태로 변경 - 성공") - void completeByGuard() { + void changeCallbackStatusToCompleteByGuardByGuard() { //given Long memberId = 1L; Long callbackId = 1L; @@ -232,7 +233,7 @@ void completeByGuard() { when(pointRepository.findByMemberId(any())).thenReturn(Optional.of(point)); //when - callbackService.complete(memberId, callbackId); + callbackService.changeCallbackStatusToCompleteByGuard(memberId, callbackId); //then verify(callback).changeStatusToComplete(); @@ -240,7 +241,7 @@ void completeByGuard() { @Test @DisplayName("보호자가 콜백 대기 상태를 완료 생태로 변경 - 일치하는 보호자 ID가 아니어서 실패") - void completeByGuard_fail() { + void changeCallbackStatusToCompleteByGuardByGuard_fail() { //given Long memberId = 10L; Long callbackId = 1L; @@ -255,7 +256,7 @@ void completeByGuard_fail() { when(senior.getMember().getId()).thenReturn(1L); //when then - assertThrows(GuardMismatchException.class, () -> callbackService.complete(memberId, callbackId)); + assertThrows(GuardMismatchException.class, () -> callbackService.changeCallbackStatusToCompleteByGuard(memberId, callbackId)); } @Test @@ -292,4 +293,65 @@ void getAcceptedCallback_fail() { //when then assertThrows(NotExistCallbackException.class, () -> callbackService.getAcceptedCallback(memberId)); } + + @Test + @DisplayName("일정 기간동안 PendingComplete 인 콜백 자동으로 Complete 로 전환 - 성공") + void changeOldPendingCompleteToCompleteByPolicy_Success() { + // Given + Callback callback1 = mock(Callback.class); + Point point = mock(Point.class); + + when(callback1.getAssignedMemberId()).thenReturn(1L); + when(pointRepository.findByMemberId(1L)).thenReturn(Optional.of(point)); + + when(callbackRepository.findAllByStatusAndPendingCompleteTimeBefore(eq(Callback.Status.PENDING_COMPLETE), any(LocalDateTime.class))) + .thenReturn(List.of(callback1)); + + // When + callbackService.changeOldPendingCompleteToCompleteByPolicy(); + + // Then + verify(callback1, times(1)).changeStatusToComplete(); + verify(point, times(1)).earn(1500); + verify(pointLogRepository, times(1)).save(any(PointLog.class)); + } + + @Test + @DisplayName("일정 기간동안 PendingComplete 인 콜백 자동으로 Complete 로 전환 - 조건에 맞는 콜백 없을 때") + void changeOldPendingCompleteToCompleteByPolicy_Success_zeroList() { + // Given + when(callbackRepository.findAllByStatusAndPendingCompleteTimeBefore(eq(Callback.Status.PENDING_COMPLETE), any(LocalDateTime.class))) + .thenReturn(List.of()); + + // When + callbackService.changeOldPendingCompleteToCompleteByPolicy(); + + // Then + verify(pointLogRepository, times(0)).save(any(PointLog.class)); + } + + @Test + @DisplayName("LocalDateTime 의 isBefore 메서드 공부 겸 테스트, 엣지 케이스") + void localDateTimeBeforeCalculateTest() { + //given + //메서드 실행 시간 + LocalDateTime 일월3일13시00분 = LocalDateTime.of(2024, 1, 3, 13, 0); + LocalDateTime 일월3일13시10분 = LocalDateTime.of(2024, 1, 3, 13, 10); + + //콜백이 Pending Complete 상태가 된 시간 + LocalDateTime 일월1일12시59분 = LocalDateTime.of(2024, 1, 1, 12, 59); + LocalDateTime 일월1일13시00분 = LocalDateTime.of(2024, 1, 1, 13, 0); + LocalDateTime 일월1일13시01분 = LocalDateTime.of(2024, 1, 1, 13, 1); + + //when then + // 1월 3일 13시 00분에 2일전에 COMPLETE 된 콜백의 존재를 확인한다. + assertTrue(일월1일12시59분.isBefore(일월3일13시00분.minusDays(2))); + assertFalse(일월1일13시00분.isBefore(일월3일13시00분.minusDays(2))); + assertFalse(일월1일13시01분.isBefore(일월3일13시00분.minusDays(2))); + + //10분뒤인 1월 3일 13시 10분에 2일전에 COMPLETE 된 콜백의 존재를 확인한다. 결국 모두 TRUE 로 처리가 되는것을 확인. + assertTrue(일월1일12시59분.isBefore(일월3일13시10분.minusDays(2))); + assertTrue(일월1일13시00분.isBefore(일월3일13시10분.minusDays(2))); + assertTrue(일월1일13시01분.isBefore(일월3일13시10분.minusDays(2))); + } }