Skip to content

Commit

Permalink
feat : 알림 단건 읽음 표시 API 추가 (#192)
Browse files Browse the repository at this point in the history
* feat : 알림 단건 읽음 표시 API 추가

* test : 알림 읽음 관련 테스트 작성
  • Loading branch information
rladmstn authored Nov 21, 2024
1 parent 26d494b commit 97fe009
Show file tree
Hide file tree
Showing 6 changed files with 180 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
import com.gamzabat.algohub.feature.group.studygroup.exception.GroupMemberValidationException;
import com.gamzabat.algohub.feature.group.studygroup.exception.InvalidRoleException;
import com.gamzabat.algohub.feature.notice.exception.NoticeValidationException;
import com.gamzabat.algohub.feature.notification.exception.CannotFoundNotificationException;
import com.gamzabat.algohub.feature.notification.exception.CannotFoundNotificationSettingException;
import com.gamzabat.algohub.feature.notification.exception.NotificationValidationException;
import com.gamzabat.algohub.feature.problem.exception.NotBojLinkException;
import com.gamzabat.algohub.feature.problem.exception.SolvedAcApiErrorException;
import com.gamzabat.algohub.feature.solution.exception.CannotFoundSolutionException;
Expand Down Expand Up @@ -128,4 +130,15 @@ protected ResponseEntity<ErrorResponse> handler(CannotFoundNotificationSettingEx
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(new ErrorResponse(HttpStatus.NOT_FOUND.value(), e.getError(), null));
}

@ExceptionHandler(CannotFoundNotificationException.class)
protected ResponseEntity<ErrorResponse> handler(CannotFoundNotificationException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(new ErrorResponse(HttpStatus.NOT_FOUND.value(), e.getError(), null));
}

@ExceptionHandler(NotificationValidationException.class)
protected ResponseEntity<ErrorResponse> handler(NotificationValidationException e) {
return ResponseEntity.status(e.getCode()).body(new ErrorResponse(e.getCode(), e.getError(), null));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
Expand Down Expand Up @@ -50,9 +51,17 @@ public ResponseEntity<List<GetNotificationResponse>> getNotifications(@AuthedUse
}

@PatchMapping
@Operation(summary = "알림 읽음을 표시하는 API", description = "알림 탭을 연 후 닫을 때 호출하면 봤던 알림들은 읽음 처리 되는 API")
public void updateIsRead(@AuthedUser User user) {
notificationService.updateIsRead(user);
@Operation(summary = "전체 알림 읽음 표시 API", description = "알림 탭을 연 후 닫을 때 호출하면 봤던 알림들은 읽음 처리 되는 API")
public ResponseEntity<Void> readAllNotifications(@AuthedUser User user) {
notificationService.readAllNotifications(user);
return ResponseEntity.ok().build();
}

@PatchMapping("/{notificationId}")
@Operation(summary = "알림 단건 읽음 표시 API", description = "알림 하나를 클릭했을 시 해당 알림은 읽음 처리 되는 API")
public ResponseEntity<Void> readNotification(@AuthedUser User user, @PathVariable Long notificationId) {
notificationService.readNotification(user, notificationId);
return ResponseEntity.ok().build();
}

@GetMapping(value = "/settings")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.gamzabat.algohub.feature.notification.exception;

import lombok.Getter;

@Getter
public class CannotFoundNotificationException extends RuntimeException {
private final String error;

public CannotFoundNotificationException(String error) {
this.error = error;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.gamzabat.algohub.feature.notification.exception;

import lombok.Getter;

@Getter
public class NotificationValidationException extends RuntimeException {
private final int code;
private final String error;

public NotificationValidationException(int code, String error) {
this.code = code;
this.error = error;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.List;
import java.util.Map;

import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -21,7 +22,9 @@
import com.gamzabat.algohub.feature.notification.domain.NotificationSetting;
import com.gamzabat.algohub.feature.notification.dto.GetNotificationResponse;
import com.gamzabat.algohub.feature.notification.enums.NotificationCategory;
import com.gamzabat.algohub.feature.notification.exception.CannotFoundNotificationException;
import com.gamzabat.algohub.feature.notification.exception.CannotFoundNotificationSettingException;
import com.gamzabat.algohub.feature.notification.exception.NotificationValidationException;
import com.gamzabat.algohub.feature.notification.repository.EmitterRepositoryImpl;
import com.gamzabat.algohub.feature.notification.repository.NotificationRepository;
import com.gamzabat.algohub.feature.notification.repository.NotificationSettingRepository;
Expand Down Expand Up @@ -158,13 +161,21 @@ public List<GetNotificationResponse> getNotifications(User user) {
return notifications.stream().map(GetNotificationResponse::toDTO).toList();
}

public void updateIsRead(User user) {
@Transactional
public void readAllNotifications(User user) {
List<Notification> notifications = notificationRepository.findAllByUserAndIsRead(user, false);
notifications.forEach(notification -> {
notification.updateIsRead();
notificationRepository.save(notification);
});
log.info("success to read status");
notifications.forEach(Notification::updateIsRead);
log.info("success to read all notifications.");
}

@Transactional
public void readNotification(User user, Long notificationId) {
Notification notification = notificationRepository.findById(notificationId)
.orElseThrow(() -> new CannotFoundNotificationException("존재하지 않는 알림입니다."));
if (!notification.getUser().getId().equals(user.getId()))
throw new NotificationValidationException(HttpStatus.FORBIDDEN.value(), "알림의 주인이 일치하지 않습니다.");
notification.updateIsRead();
log.info("success to read notification. notificationId : {}", notificationId);
}

@Transactional
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package com.gamzabat.algohub.feature.notification.service;

import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;

import java.lang.reflect.Field;
import java.time.LocalDate;
import java.util.List;
import java.util.Optional;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.HttpStatus;

import com.gamzabat.algohub.enums.Role;
import com.gamzabat.algohub.feature.group.studygroup.domain.StudyGroup;
import com.gamzabat.algohub.feature.notification.domain.Notification;
import com.gamzabat.algohub.feature.notification.exception.CannotFoundNotificationException;
import com.gamzabat.algohub.feature.notification.exception.NotificationValidationException;
import com.gamzabat.algohub.feature.notification.repository.NotificationRepository;
import com.gamzabat.algohub.feature.user.domain.User;

@ExtendWith(MockitoExtension.class)
class NotificationServiceTest {
@InjectMocks
private NotificationService notificationService;
@Mock
private NotificationRepository notificationRepository;
private User user, user2;
private StudyGroup group;
private Notification notification1, notification2;
private final Long notificationId = 10L;
private final Long userId = 1L;

@BeforeEach
void setUp() throws NoSuchFieldException, IllegalAccessException {
user = User.builder().email("email1").password("password").nickname("nickname1")
.role(Role.USER).profileImage("image1").build();
user2 = User.builder().email("email2").password("password").nickname("nickname2")
.role(Role.USER).profileImage("image2").build();
group = StudyGroup.builder()
.name("name")
.startDate(LocalDate.now())
.endDate(LocalDate.now().plusDays(1))
.groupImage("imageUrl")
.groupCode("code")
.build();
notification1 = Notification.builder().isRead(false).studyGroup(group).user(user).message("message1").build();
notification2 = Notification.builder().isRead(false).studyGroup(group).user(user).message("message2").build();

Field userId = User.class.getDeclaredField("id");
userId.setAccessible(true);
userId.set(user, 1L);

Field notificationId = Notification.class.getDeclaredField("id");
notificationId.setAccessible(true);
notificationId.set(notification1, 10L);
notificationId.set(notification2, 20L);
}

@Test
@DisplayName("전체 알림 읽음 표시 성공")
void readAllNotifications() {
// given
when(notificationRepository.findAllByUserAndIsRead(user, false)).thenReturn(
List.of(notification1, notification2));
// when
notificationService.readAllNotifications(user);
// then
assertThat(notification1.isRead()).isTrue();
assertThat(notification2.isRead()).isTrue();
}

@Test
@DisplayName("알림 단건 읽음 표시 성공")
void readNotification() {
// given
when(notificationRepository.findById(notificationId)).thenReturn(Optional.ofNullable(notification1));
// when
notificationService.readNotification(user, notificationId);
// then
assertThat(notification1.isRead()).isTrue();
}

@Test
@DisplayName("알림 단건 읽음 표시 실패 : 존재하지 않는 알림")
void readNotificationFailed_1() {
// given
when(notificationRepository.findById(notificationId)).thenReturn(Optional.empty());
// when, then
assertThatThrownBy(() -> notificationService.readNotification(user, notificationId))
.isInstanceOf(CannotFoundNotificationException.class)
.hasFieldOrPropertyWithValue("error", "존재하지 않는 알림입니다.");
}

@Test
@DisplayName("알림 단건 읽음 표시 실패 : 알림 주인 불일치")
void readNotificationFailed_2() {
// given
when(notificationRepository.findById(notificationId)).thenReturn(Optional.of(notification1));
// when, then
assertThatThrownBy(() -> notificationService.readNotification(user2, notificationId))
.isInstanceOf(NotificationValidationException.class)
.hasFieldOrPropertyWithValue("code", HttpStatus.FORBIDDEN.value())
.hasFieldOrPropertyWithValue("error", "알림의 주인이 일치하지 않습니다.");
}
}

0 comments on commit 97fe009

Please sign in to comment.