diff --git a/src/main/java/com/somemore/domains/volunteerapply/controller/CenterVolunteerApplyCommandApiController.java b/src/main/java/com/somemore/domains/volunteerapply/controller/CenterVolunteerApplyCommandApiController.java index 1f3e97441..e063221ad 100644 --- a/src/main/java/com/somemore/domains/volunteerapply/controller/CenterVolunteerApplyCommandApiController.java +++ b/src/main/java/com/somemore/domains/volunteerapply/controller/CenterVolunteerApplyCommandApiController.java @@ -4,11 +4,12 @@ import com.somemore.domains.volunteerapply.usecase.ApproveVolunteerApplyUseCase; import com.somemore.domains.volunteerapply.usecase.RejectVolunteerApplyUseCase; import com.somemore.domains.volunteerapply.usecase.SettleVolunteerApplyFacadeUseCase; -import com.somemore.global.auth.annotation.CurrentUser; +import com.somemore.global.auth.annotation.RoleId; import com.somemore.global.common.response.ApiResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; +import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.security.access.annotation.Secured; import org.springframework.web.bind.annotation.PatchMapping; @@ -18,8 +19,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import java.util.UUID; - @Tag(name = "Center Volunteer Apply Command API", description = "봉사 활동 지원 승인, 거절, 정산 API") @RequiredArgsConstructor @RequestMapping("/api") @@ -34,7 +33,7 @@ public class CenterVolunteerApplyCommandApiController { @Operation(summary = "봉사 활동 지원 승인", description = "봉사 활동 지원을 승인합니다.") @PatchMapping("/volunteer-apply/{id}/approve") public ApiResponse approve( - @CurrentUser UUID centerId, + @RoleId UUID centerId, @PathVariable Long id ) { @@ -46,7 +45,7 @@ public ApiResponse approve( @Operation(summary = "봉사 활동 지원 거절", description = "봉사 활동 지원을 거절합니다.") @PatchMapping("/volunteer-apply/{id}/reject") public ApiResponse reject( - @CurrentUser UUID centerId, + @RoleId UUID centerId, @PathVariable Long id ) { @@ -58,7 +57,7 @@ public ApiResponse reject( @Operation(summary = "봉사 활동 지원 정산", description = "봉사 활동 지원을 정산(참석 처리, 봉사 시간 부여)합니다.") @PostMapping("/volunteer-applies/settle") public ApiResponse settle( - @CurrentUser UUID centerId, + @RoleId UUID centerId, @Valid @RequestBody VolunteerApplySettleRequestDto requestDto ) { settleVolunteerApplyFacadeUseCase.settleVolunteerApplies(requestDto, centerId); diff --git a/src/main/java/com/somemore/domains/volunteerapply/controller/VolunteerApplyCommandApiController.java b/src/main/java/com/somemore/domains/volunteerapply/controller/VolunteerApplyCommandApiController.java index a98ada482..910a08653 100644 --- a/src/main/java/com/somemore/domains/volunteerapply/controller/VolunteerApplyCommandApiController.java +++ b/src/main/java/com/somemore/domains/volunteerapply/controller/VolunteerApplyCommandApiController.java @@ -3,11 +3,12 @@ import com.somemore.domains.volunteerapply.dto.request.VolunteerApplyCreateRequestDto; import com.somemore.domains.volunteerapply.usecase.ApplyVolunteerApplyUseCase; import com.somemore.domains.volunteerapply.usecase.WithdrawVolunteerApplyUseCase; -import com.somemore.global.auth.annotation.CurrentUser; +import com.somemore.global.auth.annotation.RoleId; import com.somemore.global.common.response.ApiResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; +import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.security.access.annotation.Secured; import org.springframework.web.bind.annotation.DeleteMapping; @@ -17,8 +18,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import java.util.UUID; - @Tag(name = "Volunteer Apply Command API", description = "봉사 활동 지원, 철회 관련 API") @RequiredArgsConstructor @RequestMapping("/api") @@ -32,7 +31,7 @@ public class VolunteerApplyCommandApiController { @Operation(summary = "봉사 활동 지원", description = "봉사 활동에 지원합니다.") @PostMapping("/volunteer-apply") public ApiResponse apply( - @CurrentUser UUID volunteerId, + @RoleId UUID volunteerId, @Valid @RequestBody VolunteerApplyCreateRequestDto requestDto ) { return ApiResponse.ok( @@ -46,7 +45,7 @@ public ApiResponse apply( @Operation(summary = "봉사 활동 지원 철회", description = "봉사 활동 지원을 철회합니다.") @DeleteMapping("/volunteer-apply/{id}") public ApiResponse withdraw( - @CurrentUser UUID volunteerId, + @RoleId UUID volunteerId, @PathVariable Long id ) { withdrawVolunteerApplyUseCase.withdraw(id, volunteerId); diff --git a/src/main/java/com/somemore/domains/volunteerapply/service/SettleVolunteerApplyFacadeService.java b/src/main/java/com/somemore/domains/volunteerapply/service/SettleVolunteerApplyFacadeService.java index 75aa0389d..af78eb40c 100644 --- a/src/main/java/com/somemore/domains/volunteerapply/service/SettleVolunteerApplyFacadeService.java +++ b/src/main/java/com/somemore/domains/volunteerapply/service/SettleVolunteerApplyFacadeService.java @@ -1,9 +1,12 @@ package com.somemore.domains.volunteerapply.service; +import static com.somemore.global.exception.ExceptionMessage.RECRUIT_BOARD_ID_MISMATCH; +import static com.somemore.global.exception.ExceptionMessage.UNAUTHORIZED_RECRUIT_BOARD; +import static com.somemore.global.exception.ExceptionMessage.VOLUNTEER_APPLY_LIST_MISMATCH; + import com.somemore.domains.notification.domain.NotificationSubType; import com.somemore.domains.recruitboard.domain.RecruitBoard; import com.somemore.domains.recruitboard.usecase.RecruitBoardQueryUseCase; -import com.somemore.domains.volunteer.usecase.UpdateVolunteerUseCase; import com.somemore.domains.volunteerapply.domain.VolunteerApply; import com.somemore.domains.volunteerapply.dto.request.VolunteerApplySettleRequestDto; import com.somemore.domains.volunteerapply.event.VolunteerReviewRequestEvent; @@ -13,17 +16,13 @@ import com.somemore.global.common.event.ServerEventPublisher; import com.somemore.global.common.event.ServerEventType; import com.somemore.global.exception.BadRequestException; +import com.somemore.volunteer.usecase.UpdateVolunteerUseCase; +import java.util.List; +import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.List; -import java.util.UUID; - -import static com.somemore.global.exception.ExceptionMessage.RECRUIT_BOARD_ID_MISMATCH; -import static com.somemore.global.exception.ExceptionMessage.UNAUTHORIZED_RECRUIT_BOARD; -import static com.somemore.global.exception.ExceptionMessage.VOLUNTEER_APPLY_LIST_MISMATCH; - @RequiredArgsConstructor @Transactional @Service @@ -71,7 +70,7 @@ private void validateVolunteerApplyExistence(List ids, List applies, - Long recruitBoardId) { + Long recruitBoardId) { boolean anyMismatch = applies.stream() .anyMatch(apply -> !apply.getRecruitBoardId().equals(recruitBoardId)); @@ -80,7 +79,8 @@ private void validateRecruitBoardConsistency(List applies, } } - private void publishVolunteerReviewRequestEvent(VolunteerApply apply, RecruitBoard recruitBoard) { + private void publishVolunteerReviewRequestEvent(VolunteerApply apply, + RecruitBoard recruitBoard) { VolunteerReviewRequestEvent event = VolunteerReviewRequestEvent.builder() .type(ServerEventType.NOTIFICATION) .subType(NotificationSubType.VOLUNTEER_REVIEW_REQUEST) diff --git a/src/main/java/com/somemore/volunteer/domain/NEWVolunteer.java b/src/main/java/com/somemore/volunteer/domain/NEWVolunteer.java index 0a178c674..4a1445f24 100644 --- a/src/main/java/com/somemore/volunteer/domain/NEWVolunteer.java +++ b/src/main/java/com/somemore/volunteer/domain/NEWVolunteer.java @@ -43,17 +43,27 @@ public class NEWVolunteer extends BaseEntity { @Column(name = "tier", nullable = false, length = 20) private Tier tier; + @Column(name = "total_volunteer_hours", nullable = false) + private int totalVolunteerHours; + + @Column(name = "total_volunteer_count", nullable = false) + private int totalVolunteerCount; + @Builder private NEWVolunteer( UUID userId, String nickname, Gender gender, - Tier tier + Tier tier, + int totalVolunteerHours, + int totalVolunteerCount ) { this.userId = userId; this.nickname = nickname; this.gender = gender; this.tier = tier; + this.totalVolunteerHours = totalVolunteerHours; + this.totalVolunteerCount = totalVolunteerCount; } public static NEWVolunteer createDefault(UUID userId) { @@ -62,6 +72,14 @@ public static NEWVolunteer createDefault(UUID userId) { .nickname(userId.toString().substring(0, 8)) .gender(Gender.getDefault()) .tier(Tier.getDefault()) + .totalVolunteerHours(0) + .totalVolunteerCount(0) .build(); } + + public void updateVolunteerStats(int hours, int count) { + this.totalVolunteerHours += hours; + this.totalVolunteerCount += count; + } + } diff --git a/src/main/java/com/somemore/domains/volunteer/service/UpdateVolunteerLockService.java b/src/main/java/com/somemore/volunteer/service/UpdateVolunteerLockService.java similarity index 79% rename from src/main/java/com/somemore/domains/volunteer/service/UpdateVolunteerLockService.java rename to src/main/java/com/somemore/volunteer/service/UpdateVolunteerLockService.java index fd19a3434..665cc93a5 100644 --- a/src/main/java/com/somemore/domains/volunteer/service/UpdateVolunteerLockService.java +++ b/src/main/java/com/somemore/volunteer/service/UpdateVolunteerLockService.java @@ -1,26 +1,25 @@ -package com.somemore.domains.volunteer.service; +package com.somemore.volunteer.service; + +import static com.somemore.global.exception.ExceptionMessage.NOT_EXISTS_VOLUNTEER; -import com.somemore.domains.volunteer.domain.Volunteer; -import com.somemore.domains.volunteer.repository.VolunteerRepository; -import com.somemore.domains.volunteer.usecase.UpdateVolunteerUseCase; import com.somemore.global.exception.BadRequestException; +import com.somemore.volunteer.domain.NEWVolunteer; +import com.somemore.volunteer.repository.NEWVolunteerRepository; +import com.somemore.volunteer.usecase.UpdateVolunteerUseCase; +import java.util.UUID; +import java.util.concurrent.TimeUnit; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.springframework.stereotype.Service; -import java.util.UUID; -import java.util.concurrent.TimeUnit; - -import static com.somemore.global.exception.ExceptionMessage.NOT_EXISTS_VOLUNTEER; - @Slf4j @RequiredArgsConstructor @Service public class UpdateVolunteerLockService implements UpdateVolunteerUseCase { - private final VolunteerRepository volunteerRepository; + private final NEWVolunteerRepository volunteerRepository; private final RedissonClient redissonClient; @Override @@ -47,7 +46,7 @@ public void updateVolunteerStats(UUID id, int hours) { } private void updateStatsWithLock(UUID id, int hours) { - Volunteer volunteer = volunteerRepository.findById(id).orElseThrow( + NEWVolunteer volunteer = volunteerRepository.findById(id).orElseThrow( () -> new BadRequestException(NOT_EXISTS_VOLUNTEER) ); diff --git a/src/main/java/com/somemore/domains/volunteer/usecase/UpdateVolunteerUseCase.java b/src/main/java/com/somemore/volunteer/usecase/UpdateVolunteerUseCase.java similarity index 71% rename from src/main/java/com/somemore/domains/volunteer/usecase/UpdateVolunteerUseCase.java rename to src/main/java/com/somemore/volunteer/usecase/UpdateVolunteerUseCase.java index 12c740952..a5247a0c0 100644 --- a/src/main/java/com/somemore/domains/volunteer/usecase/UpdateVolunteerUseCase.java +++ b/src/main/java/com/somemore/volunteer/usecase/UpdateVolunteerUseCase.java @@ -1,4 +1,4 @@ -package com.somemore.domains.volunteer.usecase; +package com.somemore.volunteer.usecase; import java.util.UUID; diff --git a/src/test/java/com/somemore/domains/volunteer/service/UpdateVolunteerLockServiceTest.java b/src/test/java/com/somemore/domains/volunteer/service/UpdateVolunteerLockServiceTest.java deleted file mode 100644 index e76509c36..000000000 --- a/src/test/java/com/somemore/domains/volunteer/service/UpdateVolunteerLockServiceTest.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.somemore.domains.volunteer.service; - -import com.somemore.domains.volunteer.domain.Volunteer; -import com.somemore.domains.volunteer.repository.VolunteerRepository; -import com.somemore.support.IntegrationTestSupport; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; - -import java.util.UUID; -//import java.util.concurrent.CountDownLatch; -//import java.util.concurrent.ExecutorService; -//import java.util.concurrent.Executors; -//import java.util.concurrent.TimeUnit; - -import static com.somemore.global.auth.oauth.domain.OAuthProvider.NAVER; -import static org.assertj.core.api.Assertions.assertThat; - -class UpdateVolunteerLockServiceTest extends IntegrationTestSupport { - - @Autowired - private UpdateVolunteerLockService updateVolunteerLockService; - - @Autowired - private VolunteerRepository volunteerRepository; - - @AfterEach - void tearDown() { - volunteerRepository.deleteAllInBatch(); - } - - @DisplayName("봉사자 아이디와 봉사 시간으로 봉사 스탯을 업데이트할 수 있다.") - @Test - void updateVolunteerStats() { - // given - Volunteer volunteer = Volunteer.createDefault(NAVER, "naver"); - volunteerRepository.save(volunteer); - - UUID id = volunteer.getId(); - int hours = 4; - - // when - updateVolunteerLockService.updateVolunteerStats(id, hours); - - // then - Volunteer find = volunteerRepository.findById(id).orElseThrow(); - assertThat(find.getTotalVolunteerCount()).isEqualTo(1); - assertThat(find.getTotalVolunteerHours()).isEqualTo(hours); - } - -// @DisplayName("봉사시간을 업데이트 할 수 있다.(동시성 테스트)") -// @Test -// void updateVolunteerStatsWithConcurrency() throws InterruptedException { -// // given -// Volunteer volunteer = Volunteer.createDefault(NAVER, "naver"); -// volunteerRepository.save(volunteer); -// -// UUID id = volunteer.getId(); -// int hours = 4; -// int threadCnt = 100; -// -// // 스레드 풀 크기를 줄여서 경합 감소 32 -> 16 -// ExecutorService executorService = Executors.newFixedThreadPool(16); -// CountDownLatch latch = new CountDownLatch(threadCnt); -// -// // when -// for (int i = 0; i < threadCnt; i++) { -// executorService.submit(() -> { -// try { -// updateVolunteerLockService.updateVolunteerStats(id, hours); -// } finally { -// latch.countDown(); -// } -// }); -// } -// latch.await(); -// -// -// -// // then -// Volunteer find = volunteerRepository.findById(id).orElseThrow(); -// assertThat(find.getTotalVolunteerCount()).isEqualTo(threadCnt); -// assertThat(find.getTotalVolunteerHours()).isEqualTo(hours * threadCnt); -// } -} diff --git a/src/test/java/com/somemore/domains/volunteerapply/controller/CenterVolunteerApplyCommandApiControllerTest.java b/src/test/java/com/somemore/domains/volunteerapply/controller/CenterVolunteerApplyCommandApiControllerTest.java index b3502cd87..6a30ae402 100644 --- a/src/test/java/com/somemore/domains/volunteerapply/controller/CenterVolunteerApplyCommandApiControllerTest.java +++ b/src/test/java/com/somemore/domains/volunteerapply/controller/CenterVolunteerApplyCommandApiControllerTest.java @@ -13,7 +13,7 @@ import com.somemore.domains.volunteerapply.usecase.RejectVolunteerApplyUseCase; import com.somemore.domains.volunteerapply.usecase.SettleVolunteerApplyFacadeUseCase; import com.somemore.support.ControllerTestSupport; -import com.somemore.support.annotation.WithMockCustomUser; +import com.somemore.support.annotation.MockUser; import java.util.List; import java.util.UUID; import org.junit.jupiter.api.DisplayName; @@ -33,7 +33,7 @@ class CenterVolunteerApplyCommandApiControllerTest extends ControllerTestSupport @Test @DisplayName("봉사 활동 지원 승인 성공 테스트") - @WithMockCustomUser(role = "CENTER") + @MockUser(role = "ROLE_CENTER") void approve() throws Exception { // given Long id = 1L; @@ -52,7 +52,7 @@ void approve() throws Exception { @Test @DisplayName("봉사 활동 지원 거절 성공 테스트") - @WithMockCustomUser(role = "CENTER") + @MockUser(role = "ROLE_CENTER") void reject() throws Exception { // given Long id = 1L; @@ -71,7 +71,7 @@ void reject() throws Exception { @Test @DisplayName("봉사 활동 지원 정산 성공 테스트") - @WithMockCustomUser(role = "CENTER") + @MockUser(role = "ROLE_CENTER") void settle() throws Exception { // given VolunteerApplySettleRequestDto dto = VolunteerApplySettleRequestDto.builder() diff --git a/src/test/java/com/somemore/domains/volunteerapply/controller/VolunteerApplyCommandApiControllerTest.java b/src/test/java/com/somemore/domains/volunteerapply/controller/VolunteerApplyCommandApiControllerTest.java index 4d6499b74..2ebd5b993 100644 --- a/src/test/java/com/somemore/domains/volunteerapply/controller/VolunteerApplyCommandApiControllerTest.java +++ b/src/test/java/com/somemore/domains/volunteerapply/controller/VolunteerApplyCommandApiControllerTest.java @@ -13,7 +13,7 @@ import com.somemore.domains.volunteerapply.usecase.ApplyVolunteerApplyUseCase; import com.somemore.domains.volunteerapply.usecase.WithdrawVolunteerApplyUseCase; import com.somemore.support.ControllerTestSupport; -import com.somemore.support.annotation.WithMockCustomUser; +import com.somemore.support.annotation.MockUser; import java.util.UUID; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -29,7 +29,7 @@ class VolunteerApplyCommandApiControllerTest extends ControllerTestSupport { @Test @DisplayName("봉사 활동 지원 성공 테스트") - @WithMockCustomUser + @MockUser void apply() throws Exception { // given VolunteerApplyCreateRequestDto dto = VolunteerApplyCreateRequestDto.builder() @@ -57,7 +57,7 @@ void apply() throws Exception { @Test @DisplayName("봉사 활동 철회 성공 테스트") - @WithMockCustomUser + @MockUser void withdraw() throws Exception { // given Long id = 1L; diff --git a/src/test/java/com/somemore/domains/volunteerapply/service/ApplyVolunteerApplyStatusChangeServiceTest.java b/src/test/java/com/somemore/domains/volunteerapply/service/ApplyVolunteerApplyStatusChangeServiceTest.java index 5d99e2487..1a686f7c9 100644 --- a/src/test/java/com/somemore/domains/volunteerapply/service/ApplyVolunteerApplyStatusChangeServiceTest.java +++ b/src/test/java/com/somemore/domains/volunteerapply/service/ApplyVolunteerApplyStatusChangeServiceTest.java @@ -1,27 +1,32 @@ package com.somemore.domains.volunteerapply.service; +import static com.somemore.domains.recruitboard.domain.RecruitStatus.CLOSED; +import static com.somemore.domains.recruitboard.domain.RecruitStatus.RECRUITING; +import static com.somemore.domains.recruitboard.domain.VolunteerCategory.OTHER; +import static com.somemore.domains.volunteerapply.domain.ApplyStatus.WAITING; +import static com.somemore.global.exception.ExceptionMessage.DUPLICATE_APPLICATION; +import static com.somemore.global.exception.ExceptionMessage.RECRUITMENT_NOT_OPEN; +import static com.somemore.support.fixture.LocalDateTimeFixture.createStartDateTime; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + import com.somemore.domains.recruitboard.domain.RecruitBoard; +import com.somemore.domains.recruitboard.domain.RecruitStatus; +import com.somemore.domains.recruitboard.domain.RecruitmentInfo; import com.somemore.domains.recruitboard.repository.RecruitBoardRepository; import com.somemore.domains.volunteerapply.domain.VolunteerApply; import com.somemore.domains.volunteerapply.dto.request.VolunteerApplyCreateRequestDto; import com.somemore.domains.volunteerapply.repository.VolunteerApplyRepository; import com.somemore.global.exception.BadRequestException; import com.somemore.support.IntegrationTestSupport; +import java.time.LocalDateTime; +import java.util.Optional; +import java.util.UUID; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; -import java.util.Optional; -import java.util.UUID; - -import static com.somemore.domains.volunteerapply.domain.ApplyStatus.WAITING; -import static com.somemore.global.exception.ExceptionMessage.RECRUITMENT_NOT_OPEN; -import static com.somemore.support.fixture.RecruitBoardFixture.createCloseRecruitBoard; -import static com.somemore.support.fixture.RecruitBoardFixture.createRecruitBoard; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - @Transactional class ApplyVolunteerApplyStatusChangeServiceTest extends IntegrationTestSupport { @@ -38,7 +43,7 @@ class ApplyVolunteerApplyStatusChangeServiceTest extends IntegrationTestSupport @Test void apply() { // given - RecruitBoard board = createRecruitBoard(); + RecruitBoard board = createRecruitBoard(RECRUITING); recruitBoardRepository.save(board); VolunteerApplyCreateRequestDto dto = VolunteerApplyCreateRequestDto.builder() @@ -61,7 +66,7 @@ void apply() { @Test void applyWhenCLOSE() { // given - RecruitBoard board = createCloseRecruitBoard(); + RecruitBoard board = createRecruitBoard(CLOSED); recruitBoardRepository.save(board); VolunteerApplyCreateRequestDto dto = VolunteerApplyCreateRequestDto.builder() @@ -73,8 +78,8 @@ void applyWhenCLOSE() { // when // then assertThatThrownBy( - () -> volunteerApplyCommandService.apply(dto, volunteerId) - ).isInstanceOf(BadRequestException.class) + () -> volunteerApplyCommandService.apply(dto, volunteerId)) + .isInstanceOf(BadRequestException.class) .hasMessage(RECRUITMENT_NOT_OPEN.getMessage()); } @@ -82,7 +87,7 @@ void applyWhenCLOSE() { @Test void applyWhenDuplicate() { // given - RecruitBoard board = createCloseRecruitBoard(); + RecruitBoard board = createRecruitBoard(RECRUITING); recruitBoardRepository.save(board); UUID volunteerId = UUID.randomUUID(); @@ -101,9 +106,34 @@ void applyWhenDuplicate() { // when // then assertThatThrownBy( - () -> volunteerApplyCommandService.apply(dto, volunteerId) - ).isInstanceOf(BadRequestException.class) - .hasMessage(RECRUITMENT_NOT_OPEN.getMessage()); + () -> volunteerApplyCommandService.apply(dto, volunteerId)) + .isInstanceOf(BadRequestException.class) + .hasMessage(DUPLICATE_APPLICATION.getMessage()); + } + + private static RecruitBoard createRecruitBoard(RecruitStatus status) { + + LocalDateTime startDateTime = createStartDateTime(); + LocalDateTime endDateTime = startDateTime.plusHours(2); + + RecruitmentInfo recruitmentInfo = RecruitmentInfo.builder() + .region("지역") + .recruitmentCount(1) + .volunteerStartDateTime(startDateTime) + .volunteerEndDateTime(endDateTime) + .volunteerHours(2) + .volunteerCategory(OTHER) + .admitted(true) + .build(); + + return RecruitBoard.builder() + .centerId(UUID.randomUUID()) + .locationId(1L) + .title("제목") + .content("내용") + .recruitmentInfo(recruitmentInfo) + .status(status) + .build(); } } diff --git a/src/test/java/com/somemore/domains/volunteerapply/service/SettleVolunteerApplyFacadeServiceTest.java b/src/test/java/com/somemore/domains/volunteerapply/service/SettleVolunteerApplyFacadeServiceTest.java index 33a5f0a08..adeb8652a 100644 --- a/src/test/java/com/somemore/domains/volunteerapply/service/SettleVolunteerApplyFacadeServiceTest.java +++ b/src/test/java/com/somemore/domains/volunteerapply/service/SettleVolunteerApplyFacadeServiceTest.java @@ -1,30 +1,30 @@ package com.somemore.domains.volunteerapply.service; +import static com.somemore.domains.recruitboard.domain.VolunteerCategory.COUNSELING; +import static com.somemore.domains.volunteerapply.domain.ApplyStatus.APPROVED; +import static com.somemore.global.exception.ExceptionMessage.RECRUIT_BOARD_ID_MISMATCH; +import static com.somemore.global.exception.ExceptionMessage.UNAUTHORIZED_RECRUIT_BOARD; +import static com.somemore.global.exception.ExceptionMessage.VOLUNTEER_APPLY_LIST_MISMATCH; +import static com.somemore.support.fixture.RecruitBoardFixture.createCompletedRecruitBoard; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + import com.somemore.domains.recruitboard.domain.RecruitBoard; import com.somemore.domains.recruitboard.repository.RecruitBoardRepository; -import com.somemore.domains.volunteer.domain.Volunteer; -import com.somemore.domains.volunteer.repository.VolunteerRepository; import com.somemore.domains.volunteerapply.domain.VolunteerApply; import com.somemore.domains.volunteerapply.dto.request.VolunteerApplySettleRequestDto; import com.somemore.domains.volunteerapply.repository.VolunteerApplyRepository; import com.somemore.global.exception.BadRequestException; import com.somemore.support.IntegrationTestSupport; +import com.somemore.volunteer.domain.NEWVolunteer; +import com.somemore.volunteer.repository.NEWVolunteerRepository; +import java.util.List; +import java.util.UUID; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; -import java.util.List; -import java.util.UUID; - -import static com.somemore.domains.recruitboard.domain.VolunteerCategory.COUNSELING; -import static com.somemore.domains.volunteerapply.domain.ApplyStatus.APPROVED; -import static com.somemore.global.auth.oauth.domain.OAuthProvider.NAVER; -import static com.somemore.global.exception.ExceptionMessage.*; -import static com.somemore.support.fixture.RecruitBoardFixture.createCompletedRecruitBoard; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - @Transactional class SettleVolunteerApplyFacadeServiceTest extends IntegrationTestSupport { @@ -38,7 +38,7 @@ class SettleVolunteerApplyFacadeServiceTest extends IntegrationTestSupport { private VolunteerApplyRepository volunteerApplyRepository; @Autowired - private VolunteerRepository volunteerRepository; + private NEWVolunteerRepository volunteerRepository; @DisplayName("봉사 활동 지원을 정산할 수 있다.") @Test @@ -46,9 +46,9 @@ void settleVolunteerApplies() { // given UUID centerId = UUID.randomUUID(); - Volunteer volunteer1 = volunteerRepository.save(createVolunteer()); - Volunteer volunteer2 = volunteerRepository.save(createVolunteer()); - Volunteer volunteer3 = volunteerRepository.save(createVolunteer()); + NEWVolunteer volunteer1 = volunteerRepository.save(createVolunteer()); + NEWVolunteer volunteer2 = volunteerRepository.save(createVolunteer()); + NEWVolunteer volunteer3 = volunteerRepository.save(createVolunteer()); RecruitBoard board = createCompletedRecruitBoard(centerId, COUNSELING); recruitBoardRepository.save(board); @@ -75,9 +75,12 @@ void settleVolunteerApplies() { assertThat(findApply2.getAttended()).isTrue(); assertThat(findApply3.getAttended()).isTrue(); - Volunteer findVolunteer1 = volunteerRepository.findById(volunteer1.getId()).orElseThrow(); - Volunteer findVolunteer2 = volunteerRepository.findById(volunteer2.getId()).orElseThrow(); - Volunteer findVolunteer3 = volunteerRepository.findById(volunteer3.getId()).orElseThrow(); + NEWVolunteer findVolunteer1 = volunteerRepository.findById(volunteer1.getId()) + .orElseThrow(); + NEWVolunteer findVolunteer2 = volunteerRepository.findById(volunteer2.getId()) + .orElseThrow(); + NEWVolunteer findVolunteer3 = volunteerRepository.findById(volunteer3.getId()) + .orElseThrow(); assertThat(findVolunteer1.getTotalVolunteerHours()).isEqualTo(hour); assertThat(findVolunteer2.getTotalVolunteerHours()).isEqualTo(hour); @@ -94,7 +97,7 @@ void settleVolunteerAppliesWhenNotExistId() { // given UUID centerId = UUID.randomUUID(); - Volunteer volunteer = volunteerRepository.save(createVolunteer()); + NEWVolunteer volunteer = volunteerRepository.save(createVolunteer()); RecruitBoard board = createCompletedRecruitBoard(centerId, COUNSELING); recruitBoardRepository.save(board); @@ -109,8 +112,8 @@ void settleVolunteerAppliesWhenNotExistId() { // when // then assertThatThrownBy( - () -> settleVolunteerApplyFacadeService.settleVolunteerApplies(dto, centerId) - ).isInstanceOf(BadRequestException.class) + () -> settleVolunteerApplyFacadeService.settleVolunteerApplies(dto, centerId)) + .isInstanceOf(BadRequestException.class) .hasMessage(VOLUNTEER_APPLY_LIST_MISMATCH.getMessage()); } @@ -121,8 +124,8 @@ void settleVolunteerAppliesWhenMismatchApplyAndRecruitBoard() { // given UUID centerId = UUID.randomUUID(); - Volunteer volunteer1 = volunteerRepository.save(createVolunteer()); - Volunteer volunteer2 = volunteerRepository.save(createVolunteer()); + NEWVolunteer volunteer1 = volunteerRepository.save(createVolunteer()); + NEWVolunteer volunteer2 = volunteerRepository.save(createVolunteer()); RecruitBoard board = createCompletedRecruitBoard(centerId, COUNSELING); recruitBoardRepository.save(board); @@ -138,8 +141,8 @@ void settleVolunteerAppliesWhenMismatchApplyAndRecruitBoard() { // when // then assertThatThrownBy( - () -> settleVolunteerApplyFacadeService.settleVolunteerApplies(dto, centerId) - ).isInstanceOf(BadRequestException.class) + () -> settleVolunteerApplyFacadeService.settleVolunteerApplies(dto, centerId)) + .isInstanceOf(BadRequestException.class) .hasMessage(RECRUIT_BOARD_ID_MISMATCH.getMessage()); } @@ -149,8 +152,8 @@ void settleVolunteerAppliesWhenNoAuthCenter() { // given UUID wrongId = UUID.randomUUID(); - Volunteer volunteer1 = volunteerRepository.save(createVolunteer()); - Volunteer volunteer2 = volunteerRepository.save(createVolunteer()); + NEWVolunteer volunteer1 = volunteerRepository.save(createVolunteer()); + NEWVolunteer volunteer2 = volunteerRepository.save(createVolunteer()); RecruitBoard board = createCompletedRecruitBoard(UUID.randomUUID(), COUNSELING); recruitBoardRepository.save(board); @@ -166,13 +169,13 @@ void settleVolunteerAppliesWhenNoAuthCenter() { // when // then assertThatThrownBy( - () -> settleVolunteerApplyFacadeService.settleVolunteerApplies(dto, wrongId) - ).isInstanceOf(BadRequestException.class) + () -> settleVolunteerApplyFacadeService.settleVolunteerApplies(dto, wrongId)) + .isInstanceOf(BadRequestException.class) .hasMessage(UNAUTHORIZED_RECRUIT_BOARD.getMessage()); } - private static Volunteer createVolunteer() { - return Volunteer.createDefault(NAVER, "naver"); + private static NEWVolunteer createVolunteer() { + return NEWVolunteer.createDefault(UUID.randomUUID()); } private static VolunteerApply createApply(UUID volunteerId, Long recruitId) { diff --git a/src/test/java/com/somemore/volunteer/domain/NEWVolunteerTest.java b/src/test/java/com/somemore/volunteer/domain/NEWVolunteerTest.java new file mode 100644 index 000000000..c0b0e5922 --- /dev/null +++ b/src/test/java/com/somemore/volunteer/domain/NEWVolunteerTest.java @@ -0,0 +1,33 @@ +package com.somemore.volunteer.domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.UUID; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class NEWVolunteerTest { + + @DisplayName("봉사 스텟을 업데이트할 수 있다") + @Test + void updateVolunteerStats() { + // given + int hour = 8; + int cnt = 1; + NEWVolunteer volunteer = createVolunteer(); + + // when + volunteer.updateVolunteerStats(hour, cnt); + + // then + assertThat(volunteer.getTotalVolunteerCount()).isEqualTo(cnt); + assertThat(volunteer.getTotalVolunteerHours()).isEqualTo(hour); + } + + private NEWVolunteer createVolunteer() { + UUID userId = UUID.randomUUID(); + + return NEWVolunteer.createDefault(userId); + } + +} diff --git a/src/test/java/com/somemore/volunteer/service/UpdateVolunteerLockServiceTest.java b/src/test/java/com/somemore/volunteer/service/UpdateVolunteerLockServiceTest.java new file mode 100644 index 000000000..00f93b9b6 --- /dev/null +++ b/src/test/java/com/somemore/volunteer/service/UpdateVolunteerLockServiceTest.java @@ -0,0 +1,130 @@ +package com.somemore.volunteer.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.BDDMockito.given; + +import com.somemore.global.exception.BadRequestException; +import com.somemore.global.exception.ExceptionMessage; +import com.somemore.support.IntegrationTestSupport; +import com.somemore.volunteer.domain.NEWVolunteer; +import com.somemore.volunteer.repository.NEWVolunteerJpaRepository; +import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.redisson.api.RLock; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; + +class UpdateVolunteerLockServiceTest extends IntegrationTestSupport { + + @Autowired + private UpdateVolunteerLockService updateVolunteerLockService; + + @Autowired + private NEWVolunteerJpaRepository volunteerRepository; + + @MockBean + private RLock lock; + + @AfterEach + void tearDown() { + volunteerRepository.deleteAllInBatch(); + } + + @DisplayName("봉사자 아이디와 봉사 시간으로 봉사 스탯을 업데이트할 수 있다.") + @Test + void updateVolunteerStats() { + // given + NEWVolunteer volunteer = createVolunteer(); + volunteerRepository.save(volunteer); + + UUID id = volunteer.getId(); + int hours = 4; + + // when + updateVolunteerLockService.updateVolunteerStats(id, hours); + + // then + NEWVolunteer find = volunteerRepository.findById(id).orElseThrow(); + assertThat(find.getTotalVolunteerCount()).isEqualTo(1); + assertThat(find.getTotalVolunteerHours()).isEqualTo(hours); + } + + @DisplayName("봉사시간을 업데이트 할 수 있다.(동시성 테스트)") + @Test + void updateVolunteerStatsWithConcurrency() throws InterruptedException { + // given + NEWVolunteer volunteer = createVolunteer(); + volunteerRepository.save(volunteer); + + UUID id = volunteer.getId(); + int hours = 4; + int threadCnt = 100; + + // 스레드 풀 크기를 줄여서 경합 감소 32 -> 16 + ExecutorService executorService = Executors.newFixedThreadPool(16); + CountDownLatch latch = new CountDownLatch(threadCnt); + + // when + for (int i = 0; i < threadCnt; i++) { + executorService.submit(() -> { + try { + updateVolunteerLockService.updateVolunteerStats(id, hours); + } finally { + latch.countDown(); + } + }); + } + latch.await(); + + // then + NEWVolunteer find = volunteerRepository.findById(id).orElseThrow(); + assertThat(find.getTotalVolunteerCount()).isEqualTo(threadCnt); + assertThat(find.getTotalVolunteerHours()).isEqualTo(hours * threadCnt); + } + + @DisplayName("봉사활동 정보 업데이트 중 인터럽트 예외 발생") + @Test + void UpdateVolunteerStatsWhenInterruptedException() throws InterruptedException { + // given + UUID id = UUID.randomUUID(); + int hours = 5; + + given(lock.tryLock(anyLong(), anyLong(), any())) + .willThrow(new InterruptedException()); + + // when + // then + assertThatThrownBy( + () -> updateVolunteerLockService.updateVolunteerStats(id, hours)) + .isInstanceOf(RuntimeException.class); + + } + + @DisplayName("존재하지 않는 봉사자의 봉사활동을 업데이트하려는 경우 예외가 발생한다.") + @Test + void UpdateVolunteerStatsWhenDoesNotExistsVolunteer() { + // given + UUID wrongVolunteerId = UUID.randomUUID(); + int hours = 5; + + // when + // then + assertThatThrownBy( + () -> updateVolunteerLockService.updateVolunteerStats(wrongVolunteerId, hours)) + .isInstanceOf(BadRequestException.class) + .hasMessage(ExceptionMessage.NOT_EXISTS_VOLUNTEER.getMessage()); + } + + private static NEWVolunteer createVolunteer() { + UUID userId = UUID.randomUUID(); + return NEWVolunteer.createDefault(userId); + } +}