Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package server.yakssok.domain.feedback.application.service;


import java.util.Optional;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;
Expand All @@ -11,8 +10,6 @@
import server.yakssok.domain.feedback.domain.entity.Feedback;
import server.yakssok.domain.feedback.domain.repository.FeedbackRepository;
import server.yakssok.domain.feedback.presentation.dto.request.CreateFeedbackRequest;
import server.yakssok.domain.friend.domain.entity.Friend;
import server.yakssok.domain.friend.domain.repository.FriendRepository;
import server.yakssok.domain.notification.presentation.dto.NotificationDTO;
import server.yakssok.domain.user.application.service.UserService;
import server.yakssok.domain.user.domain.entity.User;
Expand All @@ -22,7 +19,6 @@
@RequiredArgsConstructor
public class FeedbackService {
private final FeedbackRepository feedbackRepository;
private final FriendRepository friendRepository;
private final UserService userService;
private final RabbitTemplate rabbitTemplate;
private final FeedbackQueueProperties feedbackQueueProperties;
Expand All @@ -39,11 +35,7 @@ public void sendFeedback(Long userId, CreateFeedbackRequest request) {
}

private void pushFeedBackNotification(User sender, User receiver, Feedback feedback) {
Optional<Friend> receiverFollowSender = friendRepository.findByUserIdAndFollowingId(receiver.getId(), sender.getId());

NotificationDTO notificationDTO = receiverFollowSender
.map(friend -> createMutualFeedbackNotificationDto(sender, receiver, feedback, friend))
.orElseGet(() -> createOneWayFeedbackNotificationDto(sender, receiver, feedback));
NotificationDTO notificationDTO = createFeedbackNotificationDto(sender, receiver, feedback);
pushFeedBackQueue(notificationDTO);
}

Expand All @@ -53,23 +45,12 @@ private void pushFeedBackQueue(NotificationDTO notificationDTO) {
rabbitTemplate.convertAndSend(feedbackExchange, feedbackRoutingKey, notificationDTO);
}

private static NotificationDTO createOneWayFeedbackNotificationDto(User sender, User receiver, Feedback feedback) {
return NotificationDTO.fromOneWayFollowFeedback(
private static NotificationDTO createFeedbackNotificationDto(User sender, User receiver, Feedback feedback) {
return NotificationDTO.fromFeedback(
sender.getId(),
sender.getNickName(),
receiver.getId(),
feedback
);
}

private static NotificationDTO createMutualFeedbackNotificationDto(User sender, User receiver, Feedback feedback,
Friend friend) {
return NotificationDTO.fromMutualFollowFeedback(
sender.getId(),
receiver.getId(),
receiver.getNickName(),
sender.getNickName(),
feedback
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,9 @@ public void createTodaySchedules(
public void deleteAllByMedicationIds(List<Long> medicationIds) {
medicationScheduleManager.deleteAllByMedicationIds(medicationIds);
}

public void generateDateSchedules(LocalDate date) {
List<MedicationSchedule> schedules = medicationScheduleGenerator.generateAllTodaySchedules(date.atStartOfDay());
medicationScheduleJdbcRepository.batchInsert(schedules);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package server.yakssok.domain.medication_schedule.batch.job;

import java.time.LocalDate;

import org.springframework.stereotype.Component;

import lombok.RequiredArgsConstructor;
Expand All @@ -13,4 +15,8 @@ public class MedicationScheduleJob {
public void runToday() {
medicationScheduleService.generateTodaySchedules();
}

public void runFor(LocalDate parse) {
medicationScheduleService.generateDateSchedules(parse);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
Expand All @@ -17,6 +18,7 @@
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import server.yakssok.domain.medication_schedule.application.service.MedicationScheduleService;
import server.yakssok.domain.medication_schedule.batch.job.MedicationScheduleJob;
import server.yakssok.domain.medication_schedule.presentation.dto.response.MedicationScheduleGroupResponse;
import server.yakssok.global.common.reponse.ApiResponse;
import server.yakssok.global.common.security.YakssokUserDetails;
Expand All @@ -30,6 +32,7 @@
@Tag(name = "Medication Schedule", description = "복약 스케줄 API")
public class MedicationScheduleController {
private final MedicationScheduleService medicationScheduleService;
private final MedicationScheduleJob job;

@Operation(summary = "나의 복약 스케줄 조회 (오늘)")
@GetMapping("/today")
Expand Down Expand Up @@ -92,4 +95,9 @@ public ApiResponse<MedicationScheduleGroupResponse> findFriendRangeMedicationSch
Long userId = userDetails.getUserId();
return ApiResponse.success(medicationScheduleService.getFriendRangeSchedules(userId, friendId, startDate, endDate));
}

@PostMapping("/backfill/{date}") // yyyy-MM-dd
public void backfill(@PathVariable String date) {
job.runFor(LocalDate.parse(date));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,23 +34,7 @@ public static NotificationDTO fromNotTakenMedicationSchedule(MedicationScheduleA
.build();
}

public static NotificationDTO fromMutualFollowFeedback(
Long senderId,
Long receiverId,
String receiverName,
String relationName,
Feedback feedback
) {
return NotificationDTO.builder()
.senderId(senderId)
.receiverId(receiverId)
.title(NotificationTitleUtils.createFeedbackTitleMutual(feedback.getFeedbackType(), receiverName, relationName))
.body(feedback.getMessage())
.type(feedback.getFeedbackType().toNotificationType())
.build();
}

public static NotificationDTO fromOneWayFollowFeedback(
public static NotificationDTO fromFeedback(
Long senderId,
String senderName,
Long receiverId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,18 @@

import static org.mockito.Mockito.*;

import java.util.Optional;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.*;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.amqp.rabbit.core.RabbitTemplate;

import server.yakssok.domain.feedback.domain.entity.Feedback;
import server.yakssok.domain.feedback.domain.repository.FeedbackRepository;
import server.yakssok.domain.feedback.presentation.dto.request.CreateFeedbackRequest;
import server.yakssok.domain.friend.domain.entity.Friend;
import server.yakssok.domain.friend.domain.repository.FriendRepository;
import server.yakssok.domain.notification.presentation.dto.NotificationDTO;
import server.yakssok.domain.user.application.service.UserService;
import server.yakssok.domain.user.domain.entity.User;
Expand All @@ -30,139 +26,67 @@ class FeedbackServiceTest {
private FeedbackService feedbackService;

@Mock private FeedbackRepository feedbackRepository;
@Mock private FriendRepository friendRepository;
@Mock private UserService userService;
@Mock private RabbitTemplate rabbitTemplate;
@Mock private FeedbackQueueProperties feedbackQueueProperties;

@Mock private CreateFeedbackRequest createFeedbackRequest;

@Nested
@DisplayName("sendFeedback")
class SendFeedback {

private final Long SENDER_ID = 10L;
private final Long RECEIVER_ID = 20L;

private User mockSender() {
User u = mock(User.class);
when(u.getId()).thenReturn(SENDER_ID);
return u;
}

private User mockReceiver() {
User u = mock(User.class);
when(u.getId()).thenReturn(RECEIVER_ID);
return u;
}

private void stubCommonQueueProps() {
when(feedbackQueueProperties.exchange()).thenReturn("ex.feedback");
when(feedbackQueueProperties.routingKey()).thenReturn("rk.feedback");
}

@Test
@DisplayName("상대가 나를 팔로우하지 않을 때: OneWay 알림을 만들어 큐에 푸시한다")
void oneWayFollow_pushesOneWayNotification() {
// given
User sender = mockSender();
User receiver = mockReceiver();
Feedback feedback = mock(Feedback.class);
NotificationDTO oneWayDto = mock(NotificationDTO.class);

when(sender.getNickName()).thenReturn("senderNick");
when(createFeedbackRequest.receiverId()).thenReturn(RECEIVER_ID);
when(userService.getActiveUser(SENDER_ID)).thenReturn(sender);
when(userService.getActiveUser(RECEIVER_ID)).thenReturn(receiver);

when(createFeedbackRequest.toFeedback(sender, receiver)).thenReturn(feedback);
when(friendRepository.findByUserIdAndFollowingId(RECEIVER_ID, SENDER_ID))
.thenReturn(Optional.empty());

stubCommonQueueProps();

// NotificationDTO 정적 팩토리 모킹
try (MockedStatic<NotificationDTO> staticMock = mockStatic(NotificationDTO.class)) {
staticMock.when(() ->
NotificationDTO.fromOneWayFollowFeedback(
eq(SENDER_ID),
eq("senderNick"),
eq(RECEIVER_ID),
eq(feedback)))
.thenReturn(oneWayDto);

// when
feedbackService.sendFeedback(SENDER_ID, createFeedbackRequest);

// then
verify(userService).getActiveUser(SENDER_ID);
verify(userService).getActiveUser(RECEIVER_ID);

verify(feedbackRepository).save(feedback);

// 정적 메서드가 호출되었는지 검증
staticMock.verify(() ->
NotificationDTO.fromOneWayFollowFeedback(
SENDER_ID, "senderNick", RECEIVER_ID, feedback));

// 큐 전송 검증
verify(rabbitTemplate).convertAndSend("ex.feedback", "rk.feedback", oneWayDto);
verifyNoMoreInteractions(rabbitTemplate);
}
}

@Test
@DisplayName("상대가 나를 팔로우할 때(맞팔): Mutual 알림을 만들어 큐에 푸시한다")
void mutualFollow_pushesMutualNotification() {
// given
User sender = mockSender();
User receiver = mockReceiver();
Feedback feedback = mock(Feedback.class);
Friend friend = mock(Friend.class);
NotificationDTO mutualDto = mock(NotificationDTO.class);

when(receiver.getNickName()).thenReturn("receiverNick");
when(createFeedbackRequest.receiverId()).thenReturn(RECEIVER_ID);
when(userService.getActiveUser(SENDER_ID)).thenReturn(sender);
when(userService.getActiveUser(RECEIVER_ID)).thenReturn(receiver);

when(createFeedbackRequest.toFeedback(sender, receiver)).thenReturn(feedback);

when(friendRepository.findByUserIdAndFollowingId(RECEIVER_ID, SENDER_ID))
.thenReturn(Optional.of(friend));
when(friend.getRelationName()).thenReturn("bestie");

stubCommonQueueProps();

// NotificationDTO 정적 팩토리 모킹
try (MockedStatic<NotificationDTO> staticMock = mockStatic(NotificationDTO.class)) {
staticMock.when(() ->
NotificationDTO.fromMutualFollowFeedback(
eq(SENDER_ID),
eq(RECEIVER_ID),
eq("receiverNick"),
eq("bestie"),
eq(feedback)))
.thenReturn(mutualDto);

// when
feedbackService.sendFeedback(SENDER_ID, createFeedbackRequest);

// then
verify(userService).getActiveUser(SENDER_ID);
verify(userService).getActiveUser(RECEIVER_ID);

verify(feedbackRepository).save(feedback);

// 정적 메서드 호출 검증
staticMock.verify(() ->
NotificationDTO.fromMutualFollowFeedback(
SENDER_ID, RECEIVER_ID, "receiverNick", "bestie", feedback));

// 큐 전송 검증
verify(rabbitTemplate).convertAndSend("ex.feedback", "rk.feedback", mutualDto);
verifyNoMoreInteractions(rabbitTemplate);
}
@Test
@DisplayName("피드백 전송: Feedback 저장 후 DTO를 만들어 큐에 푸시한다")
void sendFeedback_pushesNotificationToQueue() {
// given
final Long SENDER_ID = 10L;
final Long RECEIVER_ID = 20L;

User sender = mock(User.class);
User receiver = mock(User.class);
Feedback feedback = mock(Feedback.class);
NotificationDTO dto = mock(NotificationDTO.class);

when(sender.getId()).thenReturn(SENDER_ID);
when(sender.getNickName()).thenReturn("senderNick");
when(receiver.getId()).thenReturn(RECEIVER_ID);

when(createFeedbackRequest.receiverId()).thenReturn(RECEIVER_ID);
when(userService.getActiveUser(SENDER_ID)).thenReturn(sender);
when(userService.getActiveUser(RECEIVER_ID)).thenReturn(receiver);

when(createFeedbackRequest.toFeedback(sender, receiver)).thenReturn(feedback);

when(feedbackQueueProperties.exchange()).thenReturn("ex.feedback");
when(feedbackQueueProperties.routingKey()).thenReturn("rk.feedback");

// NotificationDTO 정적 팩토리 모킹
try (MockedStatic<NotificationDTO> staticMock = mockStatic(NotificationDTO.class)) {
staticMock.when(() ->
NotificationDTO.fromFeedback(
eq(SENDER_ID),
eq("senderNick"),
eq(RECEIVER_ID),
eq(feedback)))
.thenReturn(dto);

// when
feedbackService.sendFeedback(SENDER_ID, createFeedbackRequest);

// then
// 유저 조회 및 저장 호출
verify(userService).getActiveUser(SENDER_ID);
verify(userService).getActiveUser(RECEIVER_ID);
verify(feedbackRepository).save(feedback);

// 정적 메서드 호출 검증
staticMock.verify(() ->
NotificationDTO.fromFeedback(SENDER_ID, "senderNick", RECEIVER_ID, feedback));

// 큐 전송 검증
verify(feedbackQueueProperties).exchange();
verify(feedbackQueueProperties).routingKey();
verify(rabbitTemplate).convertAndSend("ex.feedback", "rk.feedback", dto);

// 불필요 상호작용 없음
verifyNoMoreInteractions(rabbitTemplate, feedbackRepository, userService, feedbackQueueProperties);
}
}
}
Loading