diff --git "a/.github/workflows/\bCI.yml" "b/.github/workflows/\bCI.yml" index ba8ee54..8490015 100644 --- "a/.github/workflows/\bCI.yml" +++ "b/.github/workflows/\bCI.yml" @@ -33,4 +33,4 @@ jobs: - name: build run: | chmod +x gradlew - ./gradlew build -x test -x openapi3 + ./gradlew build -x openapi3 diff --git a/src/main/java/com/server/bbo_gak/domain/notification/service/NotificationScheduler.java b/src/main/java/com/server/bbo_gak/domain/notification/service/NotificationScheduler.java index e9c4f6d..e441867 100644 --- a/src/main/java/com/server/bbo_gak/domain/notification/service/NotificationScheduler.java +++ b/src/main/java/com/server/bbo_gak/domain/notification/service/NotificationScheduler.java @@ -2,10 +2,9 @@ import com.server.bbo_gak.domain.notification.dao.NotificationRepository; import com.server.bbo_gak.domain.notification.entity.Notification; -import com.server.bbo_gak.domain.recruit.dao.RecruitRepository; +import com.server.bbo_gak.domain.recruit.dao.RecruitScheduleRepository; import com.server.bbo_gak.domain.recruit.entity.Recruit; import com.server.bbo_gak.domain.recruit.entity.RecruitSchedule; -import com.server.bbo_gak.domain.recruit.entity.RecruitStatusCategory; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.AbstractMap.SimpleEntry; @@ -23,8 +22,8 @@ @RequiredArgsConstructor public class NotificationScheduler { - private final RecruitRepository recruitRepository; private final NotificationRepository notificationRepository; + private final RecruitScheduleRepository recruitScheduleRepository; //TODO:스케줄러 추가시 application.yml의 ThreadPool 개수를 증가시켜줘야합니다. @@ -32,9 +31,13 @@ public class NotificationScheduler { @Transactional public void executeAtMidnight() { log.info("유저별 마감 하루 남은 공고 알림 생성"); - List allRecruits = recruitRepository.findAll(); - List> recruitsWithOneDayLeft = findRecruitsWithOneDayLeft(allRecruits); + LocalDate now = LocalDate.now(); + List scheduleList = recruitScheduleRepository.findAllByDeadLineBetween(now, + now.plusDays(1)); + + List> recruitsWithOneDayLeft = getRecruitAndScheduleMaps(scheduleList); + List notifications = createNotifications(recruitsWithOneDayLeft); notificationRepository.saveAll(notifications); @@ -54,13 +57,9 @@ public void deleteOldNotificationsAtMidnight() { notificationRepository.deleteAll(notificationsToDelete); } - private List> findRecruitsWithOneDayLeft(List allRecruits) { - return allRecruits.stream() - .filter(recruit -> !RecruitStatusCategory.isRejectionStatus(recruit.getRecruitStatus())) - .flatMap(recruit -> recruit.getScheduleList().stream() - .filter(this::isDeadlineWithinOneDay) - .map(schedule -> new SimpleEntry<>(recruit, schedule)) - ) + private List> getRecruitAndScheduleMaps(List scheduleList) { + return scheduleList.stream() + .map(schedule -> new SimpleEntry<>(schedule.getRecruit(), schedule)) .collect(Collectors.toList()); } diff --git a/src/main/java/com/server/bbo_gak/domain/notification/service/NotificationService.java b/src/main/java/com/server/bbo_gak/domain/notification/service/NotificationService.java index 409ae62..2b47c2d 100644 --- a/src/main/java/com/server/bbo_gak/domain/notification/service/NotificationService.java +++ b/src/main/java/com/server/bbo_gak/domain/notification/service/NotificationService.java @@ -8,6 +8,7 @@ import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor @@ -27,8 +28,10 @@ public List getNotificationList(User user) { .toList(); } + @Transactional public void updateAllNotificationToRead(User user) { - notificationRepository.findAllByUserAndIsReadFalse(user) - .forEach(Notification::updateReadTrue); + List notificationList = notificationRepository.findAllByUserAndIsReadFalse(user); + notificationList.forEach(Notification::updateReadTrue); + notificationRepository.saveAll(notificationList); } } diff --git a/src/main/java/com/server/bbo_gak/domain/recruit/dao/RecruitScheduleRepository.java b/src/main/java/com/server/bbo_gak/domain/recruit/dao/RecruitScheduleRepository.java new file mode 100644 index 0000000..27c0f79 --- /dev/null +++ b/src/main/java/com/server/bbo_gak/domain/recruit/dao/RecruitScheduleRepository.java @@ -0,0 +1,11 @@ +package com.server.bbo_gak.domain.recruit.dao; + +import com.server.bbo_gak.domain.recruit.entity.RecruitSchedule; +import java.time.LocalDate; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface RecruitScheduleRepository extends JpaRepository { + + List findAllByDeadLineBetween(LocalDate now, LocalDate nowPlusOneDay); +} diff --git a/src/test/java/com/server/bbo_gak/domain/auth/controller/AuthControllerTest.java b/src/test/java/com/server/bbo_gak/domain/auth/controller/AuthControllerTest.java index ed024b0..dbce768 100644 --- a/src/test/java/com/server/bbo_gak/domain/auth/controller/AuthControllerTest.java +++ b/src/test/java/com/server/bbo_gak/domain/auth/controller/AuthControllerTest.java @@ -22,9 +22,13 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.HttpMethod; +import org.springframework.test.annotation.Rollback; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.jdbc.Sql; +import org.springframework.transaction.annotation.Transactional; +@Transactional +@Rollback(true) @SpringBootTest @ActiveProfiles("test") @Sql("/auth-test-data.sql") diff --git a/src/test/java/com/server/bbo_gak/domain/card/controller/CardControllerTest.java b/src/test/java/com/server/bbo_gak/domain/card/controller/CardControllerTest.java index 941672f..9d6ec79 100644 --- a/src/test/java/com/server/bbo_gak/domain/card/controller/CardControllerTest.java +++ b/src/test/java/com/server/bbo_gak/domain/card/controller/CardControllerTest.java @@ -36,19 +36,18 @@ import org.springframework.test.context.jdbc.Sql; import org.springframework.transaction.annotation.Transactional; +@Transactional @SpringBootTest @ActiveProfiles("test") @Sql("/card-test-data.sql") public class CardControllerTest extends AbstractRestDocsTests { + private static final String DEFAULT_URL = "/api/v1"; @Autowired private CardRepository cardRepository; - @Autowired private CardTagRepository cardTagRepository; - private static final String DEFAULT_URL = "/api/v1"; - @Nested class 카드_타입_카운트_조회 { diff --git a/src/test/java/com/server/bbo_gak/domain/notification/controller/NotificationControllerTest.java b/src/test/java/com/server/bbo_gak/domain/notification/controller/NotificationControllerTest.java index 50c4614..a25e2b0 100644 --- a/src/test/java/com/server/bbo_gak/domain/notification/controller/NotificationControllerTest.java +++ b/src/test/java/com/server/bbo_gak/domain/notification/controller/NotificationControllerTest.java @@ -1,15 +1,15 @@ package com.server.bbo_gak.domain.notification.controller; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.when; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import com.server.bbo_gak.domain.notification.dao.NotificationRepository; import com.server.bbo_gak.domain.notification.dto.response.NotificationGetNumResponse; import com.server.bbo_gak.domain.notification.dto.response.NotificationGetResponse; import com.server.bbo_gak.domain.notification.entity.Notification; import com.server.bbo_gak.domain.notification.entity.NotificationType; -import com.server.bbo_gak.domain.notification.service.NotificationService; +import com.server.bbo_gak.domain.notification.service.NotificationScheduler; import com.server.bbo_gak.global.AbstractRestDocsTests; import com.server.bbo_gak.global.RestDocsFactory; import java.lang.reflect.Field; @@ -20,29 +20,39 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.HttpMethod; import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.transaction.annotation.Transactional; +@Transactional @SpringBootTest @ActiveProfiles("test") +@Sql("/notification-test-data.sql") public class NotificationControllerTest extends AbstractRestDocsTests { private static final String DEFAULT_URL = "/api/v1/notifications"; - @MockBean - private NotificationService notificationService; + @Autowired + private NotificationScheduler notificationScheduler; + + @Autowired + private NotificationRepository notificationRepository; @Autowired private RestDocsFactory restDocsFactory; + @BeforeEach + void setUp() { + notificationScheduler.executeAtMidnight(); + } + @Nested class 알림개수_조회 { @Test public void 성공() throws Exception { - NotificationGetNumResponse response = new NotificationGetNumResponse(3L); - when(notificationService.getNotificationNum(any())).thenReturn(response); + NotificationGetNumResponse response = new NotificationGetNumResponse(1L); mockMvc.perform( restDocsFactory.createRequest(DEFAULT_URL + "/num", null, HttpMethod.GET, objectMapper)) @@ -50,6 +60,8 @@ class 알림개수_조회 { .andDo( restDocsFactory.getSuccessResource("[GET] 알림 개수 조회 성공", "알림 개수 조회", "Notification", null, response)); + + assertEquals(1, notificationRepository.findAll().size()); } } @@ -88,10 +100,6 @@ void setUp() { @Test public void 성공() throws Exception { - - // NotificationGetResponse DTO 객체 생성 - when(notificationService.getNotificationList(any())).thenReturn(List.of(response)); - mockMvc.perform( restDocsFactory.createRequest(DEFAULT_URL, null, HttpMethod.GET, objectMapper)) .andExpect(status().isOk()) @@ -106,14 +114,15 @@ class 알림읽음표시_업데이트 { @Test public void 성공() throws Exception { - doNothing().when(notificationService).updateAllNotificationToRead(any()); - mockMvc.perform( restDocsFactory.createRequest(DEFAULT_URL, null, HttpMethod.PUT, objectMapper)) .andExpect(status().isOk()) .andDo( restDocsFactory.getSuccessResourceList("[PUT] 알림 읽음 표시 업데이트 성공", "알림 읽음 표시 업데이트", "Notification", List.of(), List.of())); + Notification notification = notificationRepository.findById(1L).get(); + System.out.println(notification.getTitle()); + assertTrue(notification.isRead()); } } } diff --git a/src/test/java/com/server/bbo_gak/domain/notification/service/NotificationSchedulerTest.java b/src/test/java/com/server/bbo_gak/domain/notification/service/NotificationSchedulerTest.java new file mode 100644 index 0000000..726778e --- /dev/null +++ b/src/test/java/com/server/bbo_gak/domain/notification/service/NotificationSchedulerTest.java @@ -0,0 +1,35 @@ +package com.server.bbo_gak.domain.notification.service; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.server.bbo_gak.domain.notification.dao.NotificationRepository; +import com.server.bbo_gak.domain.notification.entity.Notification; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.jdbc.Sql; + +@SpringBootTest +@ActiveProfiles("test") +@Sql("/notification-test-data.sql") +public class NotificationSchedulerTest { + + @Autowired + private NotificationScheduler notificationScheduler; + + @Autowired + private NotificationRepository notificationRepository; + + @Test + public void 자정알림생성_성공() { + + // when: 스케줄러 실행 + notificationScheduler.executeAtMidnight(); + + // then: Notification이 잘 생성되었는지 검증 + List notifications = notificationRepository.findAll(); + assertEquals(1, notifications.size()); + } +} diff --git a/src/test/resources/notification-test-data.sql b/src/test/resources/notification-test-data.sql new file mode 100644 index 0000000..cccf617 --- /dev/null +++ b/src/test/resources/notification-test-data.sql @@ -0,0 +1,37 @@ +delete +from notification; + +delete +from recruit_schedule; + +delete +from recruit; + +delete +from recruit_season; + +delete +from users; + + +INSERT INTO users (deleted, created_at, update_at, user_id, dtype, email, login_id, name, password, role) +VALUES (false, '2024-07-24 21:27:20.000000', '2024-07-24 21:27:21.000000', 1, '1', null, 'test', 'test', 'test', + 'USER'); + +-- 다음으로 recruit_season 테이블에 데이터를 삽입합니다. +INSERT INTO recruit_season (recruit_season_id, name, user_id) +VALUES (1, '2024 상반기', 1); +INSERT INTO recruit_season (recruit_season_id, name, user_id) +VALUES (2, '2024 하반기', 1); + +INSERT INTO recruit (recruit_id, title, site_url, recruit_status, recruit_season_id, user_id, created_at, update_at, + deleted) +VALUES (1, 'Title for one day left', 'http://example.com/1', 'DOCUMENT_PASSED', 1, 1, CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP, false), + (2, 'Title for more than one day left', 'http://example.com/2', 'PREPARATION_IN_PROGRESS', 2, 1, + CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, false); + +-- RecruitSchedule 테이블에 데이터 삽입 +INSERT INTO recruit_schedule (recruit_schedule_id, recruit_id, recruit_schedule_stage, dead_line) +VALUES (1, 1, 'FIRST_INTERVIEW', CURRENT_DATE + 1), -- 하루 남은 스케줄 + (2, 2, 'CLOSING_DOCUMENT', CURRENT_DATE + 2); -- 하루 이상 남은 스케줄 \ No newline at end of file