diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 872194b9..1bc58047 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -5,7 +5,6 @@ on: branches: - develop - 'weekly/**' - - 142-refactor-cd-update jobs: deploy: diff --git a/.gitignore b/.gitignore index b502fe8e..f8c40ac3 100644 --- a/.gitignore +++ b/.gitignore @@ -56,4 +56,5 @@ splanet-db ### env ### .env /src/main/resources/env.properties +/src/main/resources/env.properties.base64 splanet-firebase.json \ No newline at end of file diff --git a/src/main/java/com/splanet/splanet/core/exception/ErrorCode.java b/src/main/java/com/splanet/splanet/core/exception/ErrorCode.java index 145a5913..b96e6769 100644 --- a/src/main/java/com/splanet/splanet/core/exception/ErrorCode.java +++ b/src/main/java/com/splanet/splanet/core/exception/ErrorCode.java @@ -54,6 +54,10 @@ public enum ErrorCode { // redis REDIS_SCAN_FAILED("Redis 키 스캔 중 오류가 발생했습니다.", HttpStatus.SERVICE_UNAVAILABLE), + // fcm + TOKEN_NOT_FOUND("해당 FCM 토큰을 찾을 수 없습니다.", HttpStatus.NOT_FOUND), + + // ect INVALID_PLAN_FORMAT("", HttpStatus.BAD_REQUEST); diff --git a/src/main/java/com/splanet/splanet/gpt/service/GptService.java b/src/main/java/com/splanet/splanet/gpt/service/GptService.java index a4fc07f2..89275423 100644 --- a/src/main/java/com/splanet/splanet/gpt/service/GptService.java +++ b/src/main/java/com/splanet/splanet/gpt/service/GptService.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.splanet.splanet.core.properties.GptProperties; import com.splanet.splanet.plan.dto.PlanResponseDto; +import com.splanet.splanet.plan.dto.PlanTimeDto; import com.splanet.splanet.plan.service.PlanService; import org.springframework.ai.openai.api.OpenAiApi; import org.springframework.http.ResponseEntity; @@ -25,9 +26,9 @@ public class GptService { private static final double RESPONSE_TEMPERATURE = 0.8; private static final Map PROMPT_TEMPLATES = Map.of( - 3, "사용자 입력: \"%s\" (deviceId: %s) (groupId: %s) 기존 일정이 있다면 해당 시간과는 겹치지 않게 해 줘 기존일정:%s 현재 시간 이후로 가능한 자주 반복하여 짧고 집중적으로 일정을 완수할 수 있도록 계획을 세워줘. 시험이 포함된 경우, 시험 당일이 아닌 전날까지 준비가 완료되도록 해줘 (%s 기준). 모든 일정은 한국 시간(UTC+9)을 기준으로 설정해줘.", - 2, "사용자 입력: \"%s\" (deviceId: %s) (groupId: %s) 기존 일정이 있다면 해당 시간과는 겹치지 않게 해 줘 기존일정:%s 현재 시간 이후로 적당한 간격을 두고 모든 일정을 완수할 수 있도록 계획해줘. 시험이 포함된 경우, 시험 당일이 아닌 전날까지 준비가 완료되도록 해줘 (%s 기준). 모든 일정은 한국 시간(UTC+9)을 기준으로 설정해줘.", - 1, "사용자 입력: \"%s\" (deviceId: %s) (groupId: %s) 기존 일정이 있다면 해당 시간과는 겹치지 않게 해 줘 기존일정:%s 현재 시간 이후로 여유 있게 모든 일정을 완수할 수 있도록 계획해줘. 시험이 포함된 경우, 시험 당일이 아닌 전날까지 준비가 완료되도록 해줘 (%s 기준). 모든 일정은 한국 시간(UTC+9)을 기준으로 설정해줘." + 3, "사용자 입력: \"%s\" (deviceId: %s) (groupId: %s) 제공된 가이드라인을 따르지 않으면 페널티가 부과될 것입니다. 모든 지침을 주의깊게 읽고 그에 따라 행동하세요. 기존 startDate와 endDate사이에 일정을 생성하지 말아줘. 기존 startDate, endDate:%s 현재 시간 이후로 가능한 자주 반복하여 짧고 집중적으로 일정을 완수할 수 있도록 계획을 세워줘. 현재 시간 : (%s 기준). 모든 일정은 한국 시간(UTC+9)을 기준으로 설정해줘. 또한, 기존 일정을 생각하고 새로운 일정을 적당한 간격을 두고 배치해줘.", + 2, "사용자 입력: \"%s\" (deviceId: %s) (groupId: %s) 제공된 가이드라인을 따르지 않으면 페널티가 부과될 것입니다. 모든 지침을 주의깊게 읽고 그에 따라 행동하세요. 기존 startDate와 endDate사이에 일정을 생성하지 말아줘. 기존 startDate, endDate:%s 현재 시간 이후로 적당한 간격을 두고 모든 일정을 완수할 수 있도록 계획해줘. 현재 시간 : (%s 기준). 모든 일정은 한국 시간(UTC+9)을 기준으로 설정해줘. 또한, 기존 일정을 생각하고 새로운 일정을 적당한 간격을 두고 배치해줘.", + 1, "사용자 입력: \"%s\" (deviceId: %s) (groupId: %s) 제공된 가이드라인을 따르지 않으면 페널티가 부과될 것입니다. 모든 지침을 주의깊게 읽고 그에 따라 행동하세요. 기존 startDate와 endDate사이에 일정을 생성하지 말아줘. 기존 startDate, endDate:%s 현재 시간 이후로 여유 있게 모든 일정을 완수할 수 있도록 계획해줘. 현재 시간 : (%s 기준). 모든 일정은 한국 시간(UTC+9)을 기준으로 설정해줘. 또한, 기존 일정을 생각하고 새로운 일정을 적당한 간격을 두고 배치해줘." ); public GptService(OpenAiApi openAiApi, GptProperties gptProperties, PlanService planService, ObjectMapper objectMapper) { @@ -39,7 +40,7 @@ public GptService(OpenAiApi openAiApi, GptProperties gptProperties, PlanService public String generateResponse(String userInput, Long userId, String deviceId, int groupId) { String currentTime = getCurrentTime(); - List futurePlans = (userId != null) ? planService.getAllFuturePlansByUserId(userId) : List.of(); + List futurePlans = (userId != null) ? planService.getAllFuturePlanTimesByUserId(userId) : List.of(); String planJson = convertPlansToJson(futurePlans); String promptTemplate = PROMPT_TEMPLATES.get(groupId); String fullPrompt = String.format(promptTemplate, userInput, deviceId, groupId, planJson, currentTime); @@ -54,7 +55,7 @@ private String getCurrentTime() { return LocalDateTime.now(ZoneId.of("Asia/Seoul")).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME); } - private String convertPlansToJson(List futurePlans) { + private String convertPlansToJson(List futurePlans) { try { return objectMapper.writeValueAsString(futurePlans); } catch (JsonProcessingException e) { @@ -85,3 +86,4 @@ private String getGptResponse(OpenAiApi.ChatCompletionRequest chatRequest) { } } } + diff --git a/src/main/java/com/splanet/splanet/notification/controller/FcmTokenApi.java b/src/main/java/com/splanet/splanet/notification/controller/FcmTokenApi.java index 5eb4ead0..9b14df7c 100644 --- a/src/main/java/com/splanet/splanet/notification/controller/FcmTokenApi.java +++ b/src/main/java/com/splanet/splanet/notification/controller/FcmTokenApi.java @@ -16,35 +16,25 @@ public interface FcmTokenApi { @PostMapping("/register") - @Operation(summary = "FCM 토큰 등록", description = "유저가 FCM 토큰을 등록합니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "FCM 토큰이 성공적으로 등록되었습니다."), - @ApiResponse(responseCode = "404", description = "유저를 찾을 수 없습니다.", content = @Content) - }) ResponseEntity registerFcmToken( @AuthenticationPrincipal Long userId, @RequestBody FcmTokenRequest fcmTokenRequest ); - @PutMapping("/update") - @Operation(summary = "FCM 토큰 설정 수정", description = "알림 설정 및 알림 오프셋을 수정합니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "FCM 토큰 설정이 성공적으로 수정되었습니다."), - @ApiResponse(responseCode = "404", description = "유저를 찾을 수 없습니다.", content = @Content) - }) - ResponseEntity updateFcmTokenSettings( - @AuthenticationPrincipal Long userId, - @RequestBody FcmTokenUpdateRequest fcmTokenUpdateRequest + @PutMapping("/update/notification-enabled") + ResponseEntity updateNotificationEnabled( + @RequestParam String token, + @RequestParam Boolean isNotificationEnabled + ); + + @PutMapping("/update/notification-offset") + ResponseEntity updateNotificationOffset( + @RequestParam String token, + @RequestParam Integer notificationOffset ); @DeleteMapping("/delete") - @Operation(summary = "FCM 토큰 삭제", description = "유저의 FCM 토큰을 삭제합니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "FCM 토큰이 성공적으로 삭제되었습니다."), - @ApiResponse(responseCode = "404", description = "해당 토큰을 찾을 수 없습니다.", content = @Content) - }) ResponseEntity deleteFcmToken( - @AuthenticationPrincipal Long userId, @RequestParam String token ); } diff --git a/src/main/java/com/splanet/splanet/notification/controller/FcmTokenController.java b/src/main/java/com/splanet/splanet/notification/controller/FcmTokenController.java index 38523c35..d4b66f01 100644 --- a/src/main/java/com/splanet/splanet/notification/controller/FcmTokenController.java +++ b/src/main/java/com/splanet/splanet/notification/controller/FcmTokenController.java @@ -1,7 +1,6 @@ package com.splanet.splanet.notification.controller; import com.splanet.splanet.notification.dto.FcmTokenRequest; -import com.splanet.splanet.notification.dto.FcmTokenUpdateRequest; import com.splanet.splanet.notification.service.FcmTokenService; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -21,14 +20,20 @@ public ResponseEntity registerFcmToken(Long userId, FcmTokenRequest fcmT } @Override - public ResponseEntity updateFcmTokenSettings(Long userId, FcmTokenUpdateRequest fcmTokenUpdateRequest) { - fcmTokenService.updateFcmTokenSettings(userId, fcmTokenUpdateRequest); - return ResponseEntity.ok("FCM token 수정 완료"); + public ResponseEntity updateNotificationEnabled(String token, Boolean isNotificationEnabled) { + fcmTokenService.updateNotificationEnabled(token, isNotificationEnabled); + return ResponseEntity.ok("FCM 알림 여부 수정 완료"); } @Override - public ResponseEntity deleteFcmToken(Long userId, String token) { - fcmTokenService.deleteFcmToken(userId, token); + public ResponseEntity updateNotificationOffset(String token, Integer notificationOffset) { + fcmTokenService.updateNotificationOffset(token, notificationOffset); + return ResponseEntity.ok("FCM 알림 오프셋 수정 완료"); + } + + @Override + public ResponseEntity deleteFcmToken(String token) { + fcmTokenService.deleteFcmToken(token); return ResponseEntity.ok("FCM token 삭제 완료"); } } diff --git a/src/main/java/com/splanet/splanet/notification/repository/FcmTokenRepository.java b/src/main/java/com/splanet/splanet/notification/repository/FcmTokenRepository.java index 1f26d161..6fea10f3 100644 --- a/src/main/java/com/splanet/splanet/notification/repository/FcmTokenRepository.java +++ b/src/main/java/com/splanet/splanet/notification/repository/FcmTokenRepository.java @@ -12,4 +12,5 @@ public interface FcmTokenRepository extends JpaRepository { Optional findByUserIdAndToken(Long userId, String token); List findByUserId(Long userId); List findByUserIdIn(Collection userIds); + Optional findByToken(String token); } diff --git a/src/main/java/com/splanet/splanet/notification/service/FcmTokenService.java b/src/main/java/com/splanet/splanet/notification/service/FcmTokenService.java index 29190f26..fea0ddc0 100644 --- a/src/main/java/com/splanet/splanet/notification/service/FcmTokenService.java +++ b/src/main/java/com/splanet/splanet/notification/service/FcmTokenService.java @@ -1,4 +1,3 @@ -// src/main/java/com/splanet/splanet/notification/service/FcmTokenService.java package com.splanet.splanet.notification.service; import com.splanet.splanet.core.exception.BusinessException; @@ -34,26 +33,33 @@ public void registerFcmToken(Long userId, String token) { } @Transactional - public void updateFcmTokenSettings(Long userId, FcmTokenUpdateRequest request) { - FcmToken fcmToken = fcmTokenRepository.findByUserIdAndToken(userId, request.token()) - .orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND)); + public void updateNotificationEnabled(String token, Boolean isNotificationEnabled) { + FcmToken fcmToken = fcmTokenRepository.findByToken(token) + .orElseThrow(() -> new BusinessException(ErrorCode.TOKEN_NOT_FOUND)); fcmToken = fcmToken.toBuilder() - .isNotificationEnabled(request.isNotificationEnabled() != null - ? request.isNotificationEnabled() - : fcmToken.getIsNotificationEnabled()) - .notificationOffset(request.notificationOffset() != null - ? request.notificationOffset() - : fcmToken.getNotificationOffset()) + .isNotificationEnabled(isNotificationEnabled != null ? isNotificationEnabled : fcmToken.getIsNotificationEnabled()) .build(); fcmTokenRepository.save(fcmToken); } @Transactional - public void deleteFcmToken(Long userId, String token) { - FcmToken fcmToken = fcmTokenRepository.findByUserIdAndToken(userId, token) - .orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND)); + public void updateNotificationOffset(String token, Integer notificationOffset) { + FcmToken fcmToken = fcmTokenRepository.findByToken(token) + .orElseThrow(() -> new BusinessException(ErrorCode.TOKEN_NOT_FOUND)); + + fcmToken = fcmToken.toBuilder() + .notificationOffset(notificationOffset != null ? notificationOffset : fcmToken.getNotificationOffset()) + .build(); + + fcmTokenRepository.save(fcmToken); + } + + @Transactional + public void deleteFcmToken(String token) { + FcmToken fcmToken = fcmTokenRepository.findByToken(token) + .orElseThrow(() -> new BusinessException(ErrorCode.TOKEN_NOT_FOUND)); fcmTokenRepository.delete(fcmToken); } } diff --git a/src/main/java/com/splanet/splanet/notification/service/NotificationService.java b/src/main/java/com/splanet/splanet/notification/service/NotificationService.java index 500289ac..828d0237 100644 --- a/src/main/java/com/splanet/splanet/notification/service/NotificationService.java +++ b/src/main/java/com/splanet/splanet/notification/service/NotificationService.java @@ -42,7 +42,16 @@ public void sendNotification(FcmToken fcmToken, Plan plan) { Notification notification = new Notification(title, body); - Message message = Message.builder().setToken(fcmToken.getToken()).setNotification(notification).putData("title", plan.getTitle()).putData("title", plan.getDescription()).putData("startDate", plan.getStartDate().toString()).build(); + String clickActionUrl = "https://www.splanet.co.kr"; + + Message message = Message.builder().setToken(fcmToken.getToken()) + .setNotification(notification) + .putData("click_action", clickActionUrl) + .putData("title", plan.getTitle()) + .putData("description", plan.getDescription()) + .putData("startDate", plan.getStartDate().toString()) + .build(); + try { String response = firebaseMessaging.send(message); log.info("알림을 정상적으로 전송하였습니다. : {}", response); diff --git a/src/main/java/com/splanet/splanet/plan/dto/PlanResponseDto.java b/src/main/java/com/splanet/splanet/plan/dto/PlanResponseDto.java index 49279433..33ab0619 100644 --- a/src/main/java/com/splanet/splanet/plan/dto/PlanResponseDto.java +++ b/src/main/java/com/splanet/splanet/plan/dto/PlanResponseDto.java @@ -14,11 +14,9 @@ public class PlanResponseDto { private String description; private LocalDateTime startDate; private LocalDateTime endDate; - - @JsonIgnore private Boolean accessibility; - @JsonIgnore private Boolean isCompleted; + @JsonIgnore private LocalDateTime createdAt; @JsonIgnore diff --git a/src/main/java/com/splanet/splanet/plan/dto/PlanTimeDto.java b/src/main/java/com/splanet/splanet/plan/dto/PlanTimeDto.java new file mode 100644 index 00000000..09a951a6 --- /dev/null +++ b/src/main/java/com/splanet/splanet/plan/dto/PlanTimeDto.java @@ -0,0 +1,21 @@ +package com.splanet.splanet.plan.dto; + +import java.time.LocalDateTime; + +public class PlanTimeDto { + private LocalDateTime startDate; + private LocalDateTime endDate; + + public PlanTimeDto(LocalDateTime startDate, LocalDateTime endDate) { + this.startDate = startDate; + this.endDate = endDate; + } + + public LocalDateTime getStartDate() { + return startDate; + } + + public LocalDateTime getEndDate() { + return endDate; + } +} diff --git a/src/main/java/com/splanet/splanet/plan/repository/PlanRepository.java b/src/main/java/com/splanet/splanet/plan/repository/PlanRepository.java index 731c1f0c..5a57110d 100644 --- a/src/main/java/com/splanet/splanet/plan/repository/PlanRepository.java +++ b/src/main/java/com/splanet/splanet/plan/repository/PlanRepository.java @@ -18,4 +18,7 @@ public interface PlanRepository extends JpaRepository { @Query("SELECT p FROM Plan p JOIN FETCH p.user WHERE p.startDate > :now AND p.isCompleted = false") List findUpcomingPlans(@Param("now") LocalDateTime now); + @Query("SELECT p FROM Plan p WHERE p.user.id = :userId AND p.startDate > :currentTime") + List findAllFuturePlansByUserId(@Param("userId") Long userId, @Param("currentTime") LocalDateTime currentTime); + } \ No newline at end of file diff --git a/src/main/java/com/splanet/splanet/plan/service/PlanService.java b/src/main/java/com/splanet/splanet/plan/service/PlanService.java index d40d1796..f7dfa42c 100644 --- a/src/main/java/com/splanet/splanet/plan/service/PlanService.java +++ b/src/main/java/com/splanet/splanet/plan/service/PlanService.java @@ -2,6 +2,7 @@ import com.splanet.splanet.plan.dto.PlanRequestDto; import com.splanet.splanet.plan.dto.PlanResponseDto; +import com.splanet.splanet.plan.dto.PlanTimeDto; import com.splanet.splanet.plan.entity.Plan; import com.splanet.splanet.plan.mapper.PlanMapper; import com.splanet.splanet.plan.repository.PlanRepository; @@ -16,6 +17,7 @@ import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; +import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Set; @@ -118,11 +120,11 @@ private Plan convertToPlan(PlanCardResponseDto previewCard, User user, DateTimeF } @Transactional(readOnly = true) - public List getAllFuturePlansByUserId(Long userId) { - LocalDateTime now = LocalDateTime.now(); - List futurePlans = planRepository.findAllByUserIdAndStartDateAfter(userId, now); - return futurePlans.stream() - .map(planMapper::toResponseDto) + public List getAllFuturePlanTimesByUserId(Long userId) { + LocalDateTime currentTime = LocalDateTime.now(ZoneId.of("Asia/Seoul")); + return planRepository.findAllFuturePlansByUserId(userId, currentTime) + .stream() + .map(plan -> new PlanTimeDto(plan.getStartDate(), plan.getEndDate())) .collect(Collectors.toList()); } diff --git a/src/test/java/com/splanet/splanet/notification/service/FcmTokenServiceTest.java b/src/test/java/com/splanet/splanet/notification/service/FcmTokenServiceTest.java index 1390e6ff..766a0791 100644 --- a/src/test/java/com/splanet/splanet/notification/service/FcmTokenServiceTest.java +++ b/src/test/java/com/splanet/splanet/notification/service/FcmTokenServiceTest.java @@ -1,6 +1,7 @@ package com.splanet.splanet.notification.service; import com.splanet.splanet.core.exception.BusinessException; +import com.splanet.splanet.core.exception.ErrorCode; import com.splanet.splanet.notification.dto.FcmTokenUpdateRequest; import com.splanet.splanet.notification.entity.FcmToken; import com.splanet.splanet.notification.repository.FcmTokenRepository; @@ -8,9 +9,10 @@ import com.splanet.splanet.user.repository.UserRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.jupiter.MockitoExtension; import java.util.Optional; @@ -19,6 +21,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; +@ExtendWith(MockitoExtension.class) class FcmTokenServiceTest { @Mock @@ -30,11 +33,6 @@ class FcmTokenServiceTest { @InjectMocks private FcmTokenService fcmTokenService; - @BeforeEach - void setUp() { - MockitoAnnotations.openMocks(this); - } - @Test void FCM토큰_등록_성공() { Long userId = 1L; @@ -58,68 +56,85 @@ void setUp() { when(userRepository.findById(userId)).thenReturn(Optional.empty()); - BusinessException exception = assertThrows(BusinessException.class, () -> + assertThrows(BusinessException.class, () -> fcmTokenService.registerFcmToken(userId, token)); verify(fcmTokenRepository, never()).save(any(FcmToken.class)); } @Test - void FCM토큰_설정_수정_성공() { - Long userId = 1L; + void FCM알림여부_수정_성공() { String token = "testToken"; - FcmTokenUpdateRequest request = new FcmTokenUpdateRequest(token, true, 30); + Boolean isNotificationEnabled = true; FcmToken fcmToken = FcmToken.builder() - .user(User.builder().id(userId).build()) .token(token) .isNotificationEnabled(false) - .notificationOffset(15) .build(); - when(fcmTokenRepository.findByUserIdAndToken(userId, token)).thenReturn(Optional.of(fcmToken)); + when(fcmTokenRepository.findByToken(token)).thenReturn(Optional.of(fcmToken)); - assertDoesNotThrow(() -> fcmTokenService.updateFcmTokenSettings(userId, request)); + assertDoesNotThrow(() -> fcmTokenService.updateNotificationEnabled(token, isNotificationEnabled)); verify(fcmTokenRepository, times(1)).save(any(FcmToken.class)); } @Test - void FCM토큰_설정_수정_토큰없음_예외발생() { - Long userId = 1L; + void FCM알림여부_수정_토큰없음_예외발생() { String token = "testToken"; - FcmTokenUpdateRequest request = new FcmTokenUpdateRequest(token, true, 30); - when(fcmTokenRepository.findByUserIdAndToken(userId, token)).thenReturn(Optional.empty()); + when(fcmTokenRepository.findByToken(token)).thenReturn(Optional.empty()); - BusinessException exception = assertThrows(BusinessException.class, () -> - fcmTokenService.updateFcmTokenSettings(userId, request)); + assertThrows(BusinessException.class, () -> + fcmTokenService.updateNotificationEnabled(token, true)); verify(fcmTokenRepository, never()).save(any(FcmToken.class)); } @Test - void FCM토큰_삭제_성공() { - Long userId = 1L; + void FCM알림오프셋_수정_성공() { String token = "testToken"; + Integer notificationOffset = 30; FcmToken fcmToken = FcmToken.builder() - .user(User.builder().id(userId).build()) .token(token) + .notificationOffset(15) .build(); - when(fcmTokenRepository.findByUserIdAndToken(userId, token)).thenReturn(Optional.of(fcmToken)); + when(fcmTokenRepository.findByToken(token)).thenReturn(Optional.of(fcmToken)); - assertDoesNotThrow(() -> fcmTokenService.deleteFcmToken(userId, token)); + assertDoesNotThrow(() -> fcmTokenService.updateNotificationOffset(token, notificationOffset)); + verify(fcmTokenRepository, times(1)).save(any(FcmToken.class)); + } + + @Test + void FCM알림오프셋_수정_토큰없음_예외발생() { + String token = "testToken"; + + when(fcmTokenRepository.findByToken(token)).thenReturn(Optional.empty()); + + assertThrows(BusinessException.class, () -> + fcmTokenService.updateNotificationOffset(token, 30)); + verify(fcmTokenRepository, never()).save(any(FcmToken.class)); + } + + @Test + void FCM토큰_삭제_성공() { + String token = "testToken"; + + FcmToken fcmToken = FcmToken.builder().token(token).build(); + + when(fcmTokenRepository.findByToken(token)).thenReturn(Optional.of(fcmToken)); + + assertDoesNotThrow(() -> fcmTokenService.deleteFcmToken(token)); verify(fcmTokenRepository, times(1)).delete(fcmToken); } @Test void FCM토큰_삭제_토큰없음_예외발생() { - Long userId = 1L; String token = "testToken"; - when(fcmTokenRepository.findByUserIdAndToken(userId, token)).thenReturn(Optional.empty()); + when(fcmTokenRepository.findByToken(token)).thenReturn(Optional.empty()); - BusinessException exception = assertThrows(BusinessException.class, () -> - fcmTokenService.deleteFcmToken(userId, token)); + assertThrows(BusinessException.class, () -> + fcmTokenService.deleteFcmToken(token)); verify(fcmTokenRepository, never()).delete(any(FcmToken.class)); } -} \ No newline at end of file +}