diff --git a/src/main/java/com/example/solidconnection/custom/exception/ErrorCode.java b/src/main/java/com/example/solidconnection/custom/exception/ErrorCode.java index b04c08b35..f9e1e45b1 100644 --- a/src/main/java/com/example/solidconnection/custom/exception/ErrorCode.java +++ b/src/main/java/com/example/solidconnection/custom/exception/ErrorCode.java @@ -50,6 +50,7 @@ public enum ErrorCode { APPLY_UPDATE_LIMIT_EXCEED(HttpStatus.BAD_REQUEST.value(), "지원 정보 수정은 " + APPLICATION_UPDATE_COUNT_LIMIT + "회까지만 가능합니다."), CANT_APPLY_FOR_SAME_UNIVERSITY(HttpStatus.BAD_REQUEST.value(), "1, 2, 3지망에 동일한 대학교를 입력할 수 없습니다."), CAN_NOT_CHANGE_NICKNAME_YET(HttpStatus.BAD_REQUEST.value(), "마지막 닉네임 변경으로부터 " + MIN_DAYS_BETWEEN_NICKNAME_CHANGES + "일이 지나지 않았습니다."), + PROFILE_IMAGE_NEEDED(HttpStatus.BAD_REQUEST.value(), "프로필 이미지가 필요합니다."), // community INVALID_POST_CATEGORY(HttpStatus.BAD_REQUEST.value(),"잘못된 카테고리명입니다."), diff --git a/src/main/java/com/example/solidconnection/siteuser/controller/SiteUserController.java b/src/main/java/com/example/solidconnection/siteuser/controller/SiteUserController.java index d7fe1a1fc..443404def 100644 --- a/src/main/java/com/example/solidconnection/siteuser/controller/SiteUserController.java +++ b/src/main/java/com/example/solidconnection/siteuser/controller/SiteUserController.java @@ -1,17 +1,12 @@ package com.example.solidconnection.siteuser.controller; -import com.example.solidconnection.siteuser.dto.MyPageResponse; -import com.example.solidconnection.siteuser.dto.MyPageUpdateRequest; -import com.example.solidconnection.siteuser.dto.MyPageUpdateResponse; +import com.example.solidconnection.siteuser.dto.*; import com.example.solidconnection.siteuser.service.SiteUserService; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PatchMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import java.security.Principal; @@ -36,12 +31,19 @@ public ResponseEntity getMyPageInfoToUpdate(Principal prin .ok(myPageUpdateDto); } - @PatchMapping("/update") - public ResponseEntity updateMyPageInfo( + @PatchMapping("/update/profileImage") + public ResponseEntity updateProfileImage( Principal principal, - @Valid @RequestBody MyPageUpdateRequest myPageUpdateDto) { - MyPageUpdateResponse myPageUpdateResponse = siteUserService.update(principal.getName(), myPageUpdateDto); - return ResponseEntity - .ok(myPageUpdateResponse); + @RequestParam(value = "file", required = false) MultipartFile imageFile) { + ProfileImageUpdateResponse profileImageUpdateResponse = siteUserService.updateProfileImage(principal.getName(), imageFile); + return ResponseEntity.ok().body(profileImageUpdateResponse); + } + + @PatchMapping("/update/nickname") + public ResponseEntity updateNickname( + Principal principal, + @Valid @RequestBody NicknameUpdateRequest nicknameUpdateRequest) { + NicknameUpdateResponse nicknameUpdateResponse = siteUserService.updateNickname(principal.getName(), nicknameUpdateRequest); + return ResponseEntity.ok().body(nicknameUpdateResponse); } } diff --git a/src/main/java/com/example/solidconnection/siteuser/controller/SiteUserControllerSwagger.java b/src/main/java/com/example/solidconnection/siteuser/controller/SiteUserControllerSwagger.java index e17b41c3e..6f479f67e 100644 --- a/src/main/java/com/example/solidconnection/siteuser/controller/SiteUserControllerSwagger.java +++ b/src/main/java/com/example/solidconnection/siteuser/controller/SiteUserControllerSwagger.java @@ -52,27 +52,4 @@ public interface SiteUserControllerSwagger { } ) ResponseEntity getMyPageInfoToUpdate(Principal principal); - - @Operation( - summary = "마이 페이지 정보 수정", - requestBody = @RequestBody( - description = "업데이트할 정보", - required = true, - content = @Content( - mediaType = "application/json", - schema = @Schema(implementation = MyPageUpdateRequest.class) - ) - ), - responses = { - @ApiResponse( - responseCode = "200", - description = "마이 페이지 정보 수정 성공", - content = @Content( - mediaType = "application/json", - schema = @Schema(implementation = MyPageUpdateResponse.class) - ) - ) - } - ) - ResponseEntity updateMyPageInfo(Principal principal, @Valid @RequestBody MyPageUpdateRequest myPageUpdateDto); } diff --git a/src/main/java/com/example/solidconnection/siteuser/dto/NicknameUpdateRequest.java b/src/main/java/com/example/solidconnection/siteuser/dto/NicknameUpdateRequest.java new file mode 100644 index 000000000..4627a7451 --- /dev/null +++ b/src/main/java/com/example/solidconnection/siteuser/dto/NicknameUpdateRequest.java @@ -0,0 +1,11 @@ +package com.example.solidconnection.siteuser.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; + +public record NicknameUpdateRequest( + @NotBlank(message = "닉네임을 입력해주세요.") + @Schema(description = "변경할 닉네임", example = "NewNickname") + String nickname +) { +} diff --git a/src/main/java/com/example/solidconnection/siteuser/dto/NicknameUpdateResponse.java b/src/main/java/com/example/solidconnection/siteuser/dto/NicknameUpdateResponse.java new file mode 100644 index 000000000..5a967fa6e --- /dev/null +++ b/src/main/java/com/example/solidconnection/siteuser/dto/NicknameUpdateResponse.java @@ -0,0 +1,15 @@ +package com.example.solidconnection.siteuser.dto; + +import com.example.solidconnection.siteuser.domain.SiteUser; +import io.swagger.v3.oas.annotations.media.Schema; + +public record NicknameUpdateResponse( + @Schema(description = "업데이트된 사용자 닉네임", example = "UpdatedNickname") + String nickname +) { + public static NicknameUpdateResponse from(SiteUser siteUser) { + return new NicknameUpdateResponse( + siteUser.getNickname() + ); + } +} diff --git a/src/main/java/com/example/solidconnection/siteuser/dto/ProfileImageUpdateResponse.java b/src/main/java/com/example/solidconnection/siteuser/dto/ProfileImageUpdateResponse.java new file mode 100644 index 000000000..127578e2f --- /dev/null +++ b/src/main/java/com/example/solidconnection/siteuser/dto/ProfileImageUpdateResponse.java @@ -0,0 +1,15 @@ +package com.example.solidconnection.siteuser.dto; + +import com.example.solidconnection.siteuser.domain.SiteUser; +import io.swagger.v3.oas.annotations.media.Schema; + +public record ProfileImageUpdateResponse( + @Schema(description = "업데이트된 프로필 이미지 URL", example = "http://example.com/updated-profile.jpg") + String profileImageUrl +) { + public static ProfileImageUpdateResponse from(SiteUser siteUser) { + return new ProfileImageUpdateResponse( + siteUser.getProfileImageUrl() + ); + } +} diff --git a/src/main/java/com/example/solidconnection/siteuser/service/SiteUserService.java b/src/main/java/com/example/solidconnection/siteuser/service/SiteUserService.java index e44af3a3e..71436ff87 100644 --- a/src/main/java/com/example/solidconnection/siteuser/service/SiteUserService.java +++ b/src/main/java/com/example/solidconnection/siteuser/service/SiteUserService.java @@ -1,34 +1,36 @@ package com.example.solidconnection.siteuser.service; import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.s3.S3Service; +import com.example.solidconnection.s3.UploadedFileUrlResponse; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.siteuser.dto.MyPageResponse; -import com.example.solidconnection.siteuser.dto.MyPageUpdateRequest; -import com.example.solidconnection.siteuser.dto.MyPageUpdateResponse; +import com.example.solidconnection.siteuser.dto.*; import com.example.solidconnection.siteuser.repository.LikedUniversityRepository; import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import com.example.solidconnection.type.ImgType; import com.example.solidconnection.university.domain.LikedUniversity; import com.example.solidconnection.university.dto.UniversityInfoForApplyPreviewResponse; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.List; -import static com.example.solidconnection.custom.exception.ErrorCode.CAN_NOT_CHANGE_NICKNAME_YET; -import static com.example.solidconnection.custom.exception.ErrorCode.NICKNAME_ALREADY_EXISTED; +import static com.example.solidconnection.custom.exception.ErrorCode.*; @RequiredArgsConstructor @Service public class SiteUserService { - public static final int MIN_DAYS_BETWEEN_NICKNAME_CHANGES = 30; + public static final int MIN_DAYS_BETWEEN_NICKNAME_CHANGES = 7; public static final DateTimeFormatter NICKNAME_LAST_CHANGE_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); private final SiteUserRepository siteUserRepository; private final LikedUniversityRepository likedUniversityRepository; + private final S3Service s3Service; /* * 마이페이지 정보를 조회한다. @@ -49,31 +51,11 @@ public MyPageUpdateResponse getMyPageInfoToUpdate(String email) { return MyPageUpdateResponse.from(siteUser); } - /* - * 마이페이지 정보를 수정한다. - * - 닉네임 중복을 검증한다. - * - '닉네임 변경 최소 기간'이 지나지 않았는데 변경하려 하는지 검증한다. - * */ - @Transactional - public MyPageUpdateResponse update(String email, MyPageUpdateRequest pageUpdateRequest) { - SiteUser siteUser = siteUserRepository.getByEmail(email); - - validateNicknameDuplicated(pageUpdateRequest.nickname()); - validateNicknameNotChangedRecently(siteUser.getNicknameModifiedAt()); - - siteUser.setNickname(pageUpdateRequest.nickname()); - siteUser.setProfileImageUrl(pageUpdateRequest.profileImageUrl()); - siteUser.setNicknameModifiedAt(LocalDateTime.now()); - siteUserRepository.save(siteUser); - return MyPageUpdateResponse.from(siteUser); - } - private void validateNicknameDuplicated(String nickname) { if (siteUserRepository.existsByNickname(nickname)) { throw new CustomException(NICKNAME_ALREADY_EXISTED); } } - private void validateNicknameNotChangedRecently(LocalDateTime lastModifiedAt) { if (lastModifiedAt == null) { return; @@ -96,4 +78,44 @@ public List getWishUniversity(String emai .map(likedUniversity -> UniversityInfoForApplyPreviewResponse.from(likedUniversity.getUniversityInfoForApply())) .toList(); } + + + /* + * 프로필 이미지를 수정한다. + * */ + @Transactional + public ProfileImageUpdateResponse updateProfileImage(String email, MultipartFile imageFile) { + SiteUser siteUser = siteUserRepository.getByEmail(email); + validateProfileImage(imageFile); + + s3Service.deleteExProfile(email); + UploadedFileUrlResponse uploadedFileUrlResponse = s3Service.uploadFile(imageFile, ImgType.PROFILE); + siteUser.setProfileImageUrl(uploadedFileUrlResponse.fileUrl()); + siteUserRepository.save(siteUser); + + return ProfileImageUpdateResponse.from(siteUser); + } + + private void validateProfileImage(MultipartFile imageFile) { + if (imageFile == null || imageFile.isEmpty()) { + throw new CustomException(PROFILE_IMAGE_NEEDED); + } + } + + /* + * 닉네임을 수정한다. + * */ + @Transactional + public NicknameUpdateResponse updateNickname(String email, NicknameUpdateRequest nicknameUpdateRequest) { + SiteUser siteUser = siteUserRepository.getByEmail(email); + + validateNicknameDuplicated(nicknameUpdateRequest.nickname()); + validateNicknameNotChangedRecently(siteUser.getNicknameModifiedAt()); + + siteUser.setNickname(nicknameUpdateRequest.nickname()); + siteUser.setNicknameModifiedAt(LocalDateTime.now()); + siteUserRepository.save(siteUser); + + return NicknameUpdateResponse.from(siteUser); + } } diff --git a/src/test/java/com/example/solidconnection/e2e/MyPageUpdateTest.java b/src/test/java/com/example/solidconnection/e2e/MyPageUpdateTest.java index fa515e4f2..cb058fe3a 100644 --- a/src/test/java/com/example/solidconnection/e2e/MyPageUpdateTest.java +++ b/src/test/java/com/example/solidconnection/e2e/MyPageUpdateTest.java @@ -4,8 +4,9 @@ import com.example.solidconnection.config.token.TokenType; import com.example.solidconnection.custom.response.ErrorResponse; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.siteuser.dto.MyPageUpdateRequest; import com.example.solidconnection.siteuser.dto.MyPageUpdateResponse; +import com.example.solidconnection.siteuser.dto.NicknameUpdateRequest; +import com.example.solidconnection.siteuser.dto.NicknameUpdateResponse; import com.example.solidconnection.siteuser.repository.SiteUserRepository; import io.restassured.RestAssured; import org.junit.jupiter.api.BeforeEach; @@ -68,27 +69,26 @@ public void setUpUserAndToken() { } @Test - void 마이_페이지_정보를_수정한다() { + void 닉네임을_수정한다() { // request - body 생성 및 요청 - MyPageUpdateRequest myPageUpdateRequest = new MyPageUpdateRequest("newNickname", "newProfileImageUrl"); - MyPageUpdateResponse myPageUpdateResponse = RestAssured.given() + NicknameUpdateRequest nicknameUpdateRequest = new NicknameUpdateRequest("newNickname"); + NicknameUpdateResponse nicknameUpdateResponse = RestAssured.given() .header("Authorization", "Bearer " + accessToken) .log().all() - .body(myPageUpdateRequest) + .body(nicknameUpdateRequest) .contentType("application/json") - .patch("/my-page/update") + .patch("/my-page/update/nickname") .then().log().all() .statusCode(HttpStatus.OK.value()) - .extract().as(MyPageUpdateResponse.class); + .extract().as(NicknameUpdateResponse.class); SiteUser savedSiteUser = siteUserRepository.getByEmail(email); assertAll("마이 페이지 정보가 수정된다.", - () -> assertThat(myPageUpdateResponse.nickname()).isEqualTo(savedSiteUser.getNickname()), - () -> assertThat(myPageUpdateResponse.profileImageUrl()).isEqualTo(savedSiteUser.getProfileImageUrl())); + () -> assertThat(nicknameUpdateResponse.nickname()).isEqualTo(savedSiteUser.getNickname())); } @Test - void 마이_페이지_정보를_수정할_때_닉네임이_중복된다면_예외_응답을_반환한다() { + void 닉네임을_수정할_때_닉네임이_중복된다면_예외_응답을_반환한다() { // setUp - 같은 닉네임을 갖는 다른 회원 정보 저장 SiteUser existUser = createSiteUserByEmail("existUser"); String duplicateNickname = "duplicateNickname"; @@ -96,13 +96,13 @@ public void setUpUserAndToken() { siteUserRepository.save(existUser); // request - body 생성 및 요청 - MyPageUpdateRequest myPageUpdateRequest = new MyPageUpdateRequest(duplicateNickname, "newProfileImageUrl"); + NicknameUpdateRequest nicknameUpdateRequest = new NicknameUpdateRequest("duplicateNickname"); ErrorResponse response = RestAssured.given() .header("Authorization", "Bearer " + accessToken) .log().all() - .body(myPageUpdateRequest) + .body(nicknameUpdateRequest) .contentType("application/json") - .patch("/my-page/update") + .patch("/my-page/update/nickname") .then().log().all() .statusCode(HttpStatus.CONFLICT.value()) .extract().as(ErrorResponse.class); @@ -112,7 +112,7 @@ public void setUpUserAndToken() { } @Test - void 마이_페이지_정보를_수정할_때_닉네임_변경_가능_기한이_지나지않았다면_예외_응답을_반환한다() { + void 닉네임을_수정할_때_닉네임_변경_가능_기한이_지나지않았다면_예외_응답을_반환한다() { // setUp - 회원 정보 저장 (닉네임 변경 가능 시간이 되기 1분 전) LocalDateTime nicknameModifiedAt = LocalDateTime.now() .minusDays(MIN_DAYS_BETWEEN_NICKNAME_CHANGES) @@ -121,13 +121,13 @@ public void setUpUserAndToken() { siteUserRepository.save(siteUser); // request - body 생성 및 요청 - MyPageUpdateRequest myPageUpdateRequest = new MyPageUpdateRequest("newNickname", "newProfileImageUrl"); + NicknameUpdateRequest nicknameUpdateRequest = new NicknameUpdateRequest("newNickname"); ErrorResponse response = RestAssured.given() .header("Authorization", "Bearer " + accessToken) .log().all() - .body(myPageUpdateRequest) + .body(nicknameUpdateRequest) .contentType("application/json") - .patch("/my-page/update") + .patch("/my-page/update/nickname") .then().log().all() .statusCode(HttpStatus.BAD_REQUEST.value()) .extract().as(ErrorResponse.class); diff --git a/src/test/java/com/example/solidconnection/unit/service/SiteUserServiceTest.java b/src/test/java/com/example/solidconnection/unit/service/SiteUserServiceTest.java new file mode 100644 index 000000000..f6a330348 --- /dev/null +++ b/src/test/java/com/example/solidconnection/unit/service/SiteUserServiceTest.java @@ -0,0 +1,162 @@ +package com.example.solidconnection.unit.service; + +import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.s3.S3Service; +import com.example.solidconnection.s3.UploadedFileUrlResponse; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.dto.NicknameUpdateRequest; +import com.example.solidconnection.siteuser.dto.NicknameUpdateResponse; +import com.example.solidconnection.siteuser.dto.ProfileImageUpdateResponse; +import com.example.solidconnection.siteuser.repository.LikedUniversityRepository; +import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import com.example.solidconnection.siteuser.service.SiteUserService; +import com.example.solidconnection.type.Gender; +import com.example.solidconnection.type.ImgType; +import com.example.solidconnection.type.PreparationStatus; +import com.example.solidconnection.type.Role; +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.mock.web.MockMultipartFile; +import org.springframework.web.multipart.MultipartFile; + +import java.time.LocalDateTime; + +import static com.example.solidconnection.custom.exception.ErrorCode.*; +import static com.example.solidconnection.siteuser.service.SiteUserService.NICKNAME_LAST_CHANGE_DATE_FORMAT; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +@DisplayName("유저 서비스 테스트") +public class SiteUserServiceTest { + @InjectMocks + SiteUserService siteUserService; + @Mock + SiteUserRepository siteUserRepository; + @Mock + LikedUniversityRepository likedUniversityRepository; + @Mock + S3Service s3Service; + + private SiteUser siteUser; + private MultipartFile imageFile; + private UploadedFileUrlResponse uploadedFileUrlResponse; + + @BeforeEach + void setUp() { + siteUser = createSiteUser(); + imageFile = createMockImageFile(); + uploadedFileUrlResponse = createUploadedFileUrlResponse(); + + } + + private SiteUser createSiteUser() { + return new SiteUser( + "test@example.com", + "nickname", + "http://example.com/profile.jpg", + "1999-01-01", + PreparationStatus.CONSIDERING, + Role.MENTEE, + Gender.MALE + ); + } + + private MultipartFile createMockImageFile() { + return new MockMultipartFile("file1", "test1.png", + "image/png", "test image content 1".getBytes()); + + } + + private UploadedFileUrlResponse createUploadedFileUrlResponse() { + return new UploadedFileUrlResponse("https://s3.example.com/test1.png"); + } + + @Test + void 프로필_이미지를_수정한다() { + // Given + when(siteUserRepository.getByEmail(siteUser.getEmail())).thenReturn(siteUser); + when(s3Service.uploadFile(imageFile, ImgType.PROFILE)).thenReturn(uploadedFileUrlResponse); + + // When + ProfileImageUpdateResponse profileImageUpdateResponse = + siteUserService.updateProfileImage(siteUser.getEmail(), imageFile); + // Then + assertEquals(profileImageUpdateResponse, ProfileImageUpdateResponse.from(siteUser)); + verify(siteUserRepository, times(1)).getByEmail(siteUser.getEmail()); + verify(s3Service, times(1)).deleteExProfile(siteUser.getEmail()); + verify(s3Service, times(1)).uploadFile(imageFile, ImgType.PROFILE); + verify(siteUserRepository, times(1)).save(any(SiteUser.class)); + } + + @Test + void 프로필_이미지를_수정할_때_이미지가_없다면_예외_응답을_반환한다() { + // Given + when(siteUserRepository.getByEmail(siteUser.getEmail())).thenReturn(siteUser); + + // When & Then + CustomException exception = assertThrows(CustomException.class, () -> + siteUserService.updateProfileImage(siteUser.getEmail(), null)); + assertThat(exception.getMessage()) + .isEqualTo(PROFILE_IMAGE_NEEDED.getMessage()); + assertThat(exception.getCode()) + .isEqualTo(PROFILE_IMAGE_NEEDED.getCode()); + } + + @Test + void 닉네임을_수정한다() { + // Given + NicknameUpdateRequest nicknameUpdateRequest = new NicknameUpdateRequest("newNickname"); + when(siteUserRepository.getByEmail(siteUser.getEmail())).thenReturn(siteUser); + + // When + NicknameUpdateResponse nicknameUpdateResponse + = siteUserService.updateNickname(siteUser.getEmail(), nicknameUpdateRequest); + // Then + assertEquals( nicknameUpdateResponse, NicknameUpdateResponse.from(siteUser)); + verify(siteUserRepository, times(1)).getByEmail(siteUser.getEmail()); + verify(siteUserRepository, times(1)).save(any(SiteUser.class)); + } + + @Test + void 닉네임을_수정할_때_중복된_닉네임이라면_예외_응답을_반환한다() { + // Given + NicknameUpdateRequest nicknameUpdateRequest = new NicknameUpdateRequest("newNickname"); + when(siteUserRepository.getByEmail(siteUser.getEmail())).thenReturn(siteUser); + when(siteUserRepository.existsByNickname(nicknameUpdateRequest.nickname())).thenReturn(true); + + // When & Then + CustomException exception = assertThrows(CustomException.class, () -> + siteUserService.updateNickname(siteUser.getEmail(), nicknameUpdateRequest)); + assertThat(exception.getMessage()) + .isEqualTo(NICKNAME_ALREADY_EXISTED.getMessage()); + assertThat(exception.getCode()) + .isEqualTo(NICKNAME_ALREADY_EXISTED.getCode()); + } + + @Test + void 닉네임을_수정할_때_변경_가능_기한이_지나지_않았다면_예외_응답을_반환한다() { + // Given + NicknameUpdateRequest nicknameUpdateRequest = new NicknameUpdateRequest("newNickname"); + siteUser.setNicknameModifiedAt(LocalDateTime.now()); + when(siteUserRepository.getByEmail(siteUser.getEmail())).thenReturn(siteUser); + + // When & Then + CustomException exception = assertThrows(CustomException.class, () -> + siteUserService.updateNickname(siteUser.getEmail(), nicknameUpdateRequest)); + + String formatLastModifiedAt + = String.format("(마지막 수정 시간 : %s)", NICKNAME_LAST_CHANGE_DATE_FORMAT.format(siteUser.getNicknameModifiedAt())); + CustomException expectedException = new CustomException(CAN_NOT_CHANGE_NICKNAME_YET, formatLastModifiedAt); + assertThat(exception.getMessage()).isEqualTo(expectedException.getMessage()); + assertThat(exception.getCode()).isEqualTo(expectedException.getCode()); + } +}