diff --git a/src/main/java/com/ajou/hertz/common/file/dto/FileDto.java b/src/main/java/com/ajou/hertz/common/file/dto/FileDto.java index 0ad6c16..f919022 100644 --- a/src/main/java/com/ajou/hertz/common/file/dto/FileDto.java +++ b/src/main/java/com/ajou/hertz/common/file/dto/FileDto.java @@ -21,8 +21,4 @@ public static FileDto create( ) { return new FileDto(originalName, storedName, url); } - - public String getStoredFileUrl() { - return url; - } } diff --git a/src/main/java/com/ajou/hertz/domain/user/controller/UserController.java b/src/main/java/com/ajou/hertz/domain/user/controller/UserController.java index 081a0f7..32fd064 100644 --- a/src/main/java/com/ajou/hertz/domain/user/controller/UserController.java +++ b/src/main/java/com/ajou/hertz/domain/user/controller/UserController.java @@ -19,7 +19,6 @@ import org.springframework.web.multipart.MultipartFile; import com.ajou.hertz.common.auth.UserPrincipal; -import com.ajou.hertz.common.file.dto.FileDto; import com.ajou.hertz.common.validator.PhoneNumber; import com.ajou.hertz.domain.user.dto.UserDto; import com.ajou.hertz.domain.user.dto.request.SignUpRequest; @@ -137,9 +136,7 @@ public UserResponse updateProfileImageV1( @AuthenticationPrincipal UserPrincipal userPrincipal, @RequestPart("profileImage") MultipartFile profileImage ) { - FileDto uploadedFile = userProfileImageCommandService.uploadProfileImage(userPrincipal.getUserId(), - profileImage); - UserDto userUpdated = userCommandService.updateProfileImage(userPrincipal.getUserId(), uploadedFile); + UserDto userUpdated = userCommandService.updateUserProfileImage(userPrincipal.getUserId(), profileImage); return UserResponse.from(userUpdated); } diff --git a/src/main/java/com/ajou/hertz/domain/user/dto/request/UpdateProfileImageUrlRequest.java b/src/main/java/com/ajou/hertz/domain/user/dto/request/UpdateProfileImageUrlRequest.java deleted file mode 100644 index 66957e3..0000000 --- a/src/main/java/com/ajou/hertz/domain/user/dto/request/UpdateProfileImageUrlRequest.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.ajou.hertz.domain.user.dto.request; - -import org.springframework.web.multipart.MultipartFile; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@AllArgsConstructor(access = AccessLevel.PRIVATE) -@NoArgsConstructor(access = AccessLevel.PRIVATE) -@Getter -public class UpdateProfileImageUrlRequest { - - @Schema(description = "프로필 이미지") - private MultipartFile file; - -} diff --git a/src/main/java/com/ajou/hertz/domain/user/service/UserCommandService.java b/src/main/java/com/ajou/hertz/domain/user/service/UserCommandService.java index f400983..0d2d67b 100644 --- a/src/main/java/com/ajou/hertz/domain/user/service/UserCommandService.java +++ b/src/main/java/com/ajou/hertz/domain/user/service/UserCommandService.java @@ -6,8 +6,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; +import org.springframework.web.multipart.MultipartFile; -import com.ajou.hertz.common.file.dto.FileDto; import com.ajou.hertz.common.kakao.dto.response.KakaoUserInfoResponse; import com.ajou.hertz.common.properties.HertzProperties; import com.ajou.hertz.domain.user.constant.Gender; @@ -32,6 +32,7 @@ public class UserCommandService { private final PasswordEncoder passwordEncoder; private final HertzProperties hertzProperties; private final UserProfileImageRepository userProfileImageRepository; + private final UserProfileImageCommandService userProfileImageCommandService; /** * 새로운 회원을 등록한다. @@ -134,18 +135,15 @@ private String generateRandom16CharString() { } /** - * 전달된 이미지로 프로필 이미지 url을 변경한다. + * 유저의 프로필 이미지를 업데이트합니다. * * @param userId 유저의 ID - * @param uploadedFile 변경할 이미지 파일 + * @param profileImage 변경할 프로필 이미지 * * @return 변경된 유저 정보 */ - public UserDto updateProfileImage(Long userId, FileDto uploadedFile) { - User user = userQueryService.getById(userId); - String newProfileImageUrl = uploadedFile.getStoredFileUrl(); - user.changeProfileImageUrl(newProfileImageUrl); - return UserDto.from(user); + public UserDto updateUserProfileImage(Long userId, MultipartFile profileImage) { + return userProfileImageCommandService.updateProfileImage(userId, profileImage); } /** @@ -154,7 +152,7 @@ public UserDto updateProfileImage(Long userId, FileDto uploadedFile) { * @param userId 유저의 ID * @param contactLink 변경할 연락 수단 * - *@return 변경된 유저 정보 + * @return 변경된 유저 정보 */ public UserDto updateContactLink(Long userId, String contactLink) { User user = userQueryService.getById(userId); diff --git a/src/main/java/com/ajou/hertz/domain/user/service/UserProfileImageCommandService.java b/src/main/java/com/ajou/hertz/domain/user/service/UserProfileImageCommandService.java index cc42a7e..3c10d50 100644 --- a/src/main/java/com/ajou/hertz/domain/user/service/UserProfileImageCommandService.java +++ b/src/main/java/com/ajou/hertz/domain/user/service/UserProfileImageCommandService.java @@ -8,6 +8,7 @@ import com.ajou.hertz.common.file.dto.FileDto; import com.ajou.hertz.common.file.service.FileService; +import com.ajou.hertz.domain.user.dto.UserDto; import com.ajou.hertz.domain.user.entity.User; import com.ajou.hertz.domain.user.entity.UserProfileImage; import com.ajou.hertz.domain.user.repository.UserProfileImageRepository; @@ -29,12 +30,16 @@ public class UserProfileImageCommandService { * @param userId 유저 id * @param newProfileImage 새로운 프로필 이미지 * - * @return 업로드된 파일 정보가 담긴 dto + * @return 업데이트된 유저 정보 */ - public FileDto uploadProfileImage(Long userId, MultipartFile newProfileImage) { - User user = userQueryService.getById(userId); + public UserDto updateProfileImage(Long userId, MultipartFile newProfileImage) { + User user = userQueryService.getById(userId); Optional optionalOldProfileImage = userProfileImageRepository.findById(userId); + + String uploadPath = "user-profile-images/"; + FileDto uploadedFile = fileService.uploadFile(newProfileImage, uploadPath); + String newProfileImageUrl = uploadedFile.getUrl(); if (optionalOldProfileImage.isPresent()) { UserProfileImage oldProfileImage = optionalOldProfileImage.get(); userProfileImageRepository.delete(oldProfileImage); @@ -42,13 +47,11 @@ public FileDto uploadProfileImage(Long userId, MultipartFile newProfileImage) { fileService.deleteFile(oldProfileImage.getStoredName()); } - String uploadPath = "user-profile-images/"; - FileDto uploadedFile = fileService.uploadFile(newProfileImage, uploadPath); - - UserProfileImage newUserProfileImage = UserProfileImage.create( - user, uploadedFile.getOriginalName(), uploadedFile.getStoredName(), uploadedFile.getStoredFileUrl()); + UserProfileImage newUserProfileImage = UserProfileImage.create(user, uploadedFile.getOriginalName(), + uploadedFile.getStoredName(), newProfileImageUrl); userProfileImageRepository.save(newUserProfileImage); - return uploadedFile; + user.changeProfileImageUrl(newProfileImageUrl); + return UserDto.from(user); } } diff --git a/src/test/java/com/ajou/hertz/unit/domain/user/controller/UserControllerTest.java b/src/test/java/com/ajou/hertz/unit/domain/user/controller/UserControllerTest.java index d782366..766ddc3 100644 --- a/src/test/java/com/ajou/hertz/unit/domain/user/controller/UserControllerTest.java +++ b/src/test/java/com/ajou/hertz/unit/domain/user/controller/UserControllerTest.java @@ -31,7 +31,6 @@ import com.ajou.hertz.common.auth.JwtTokenProvider; import com.ajou.hertz.common.auth.UserPrincipal; import com.ajou.hertz.common.config.SecurityConfig; -import com.ajou.hertz.common.file.dto.FileDto; import com.ajou.hertz.domain.user.constant.Gender; import com.ajou.hertz.domain.user.constant.RoleType; import com.ajou.hertz.domain.user.controller.UserController; @@ -219,7 +218,7 @@ public void securitySetUp() throws Exception { } @Test - void 주어진_id와_새로운_프로필_이미지로_프로필_이미지를_변경한다() throws Exception { + void 주어진_id와_변경할_프로필_이미지로_프로필_이미지를_변경한다() throws Exception { // given long userId = 1L; MockMultipartFile profileImage = new MockMultipartFile( @@ -229,12 +228,9 @@ public void securitySetUp() throws Exception { "test".getBytes() ); UserDetails userDetails = createTestUser(userId); - FileDto uploadedFile = createFileDto(); UserDto expectedResult = createUserDto(userId); - given(userProfileImageCommandService.uploadProfileImage(userId, profileImage)).willReturn( - uploadedFile); - given(userCommandService.updateProfileImage(userId, uploadedFile)).willReturn(expectedResult); + given(userCommandService.updateUserProfileImage(userId, profileImage)).willReturn(expectedResult); // when & then mvc.perform( @@ -249,8 +245,7 @@ public void securitySetUp() throws Exception { ) .andExpect(status().isOk()) .andExpect(jsonPath("$.profileImageUrl").value(expectedResult.getProfileImageUrl())); - then(userProfileImageCommandService).should().uploadProfileImage(userId, profileImage); - then(userCommandService).should().updateProfileImage(userId, uploadedFile); + then(userCommandService).should().updateUserProfileImage(userId, profileImage); verifyEveryMocksShouldHaveNoMoreInteractions(); } @@ -324,12 +319,4 @@ private UserDto createUserDto() throws Exception { private UserDetails createTestUser(Long userId) throws Exception { return new UserPrincipal(createUserDto(userId)); } - - private FileDto createFileDto() throws Exception { - return ReflectionUtils.createFileDto( - "test.jpg", - "test-stored.jpg", - "https://example.com/user-profile-images/storedFileName.jpg"); - } - } \ No newline at end of file diff --git a/src/test/java/com/ajou/hertz/unit/domain/user/service/UserCommandServiceTest.java b/src/test/java/com/ajou/hertz/unit/domain/user/service/UserCommandServiceTest.java index 7ea0580..8086763 100644 --- a/src/test/java/com/ajou/hertz/unit/domain/user/service/UserCommandServiceTest.java +++ b/src/test/java/com/ajou/hertz/unit/domain/user/service/UserCommandServiceTest.java @@ -19,6 +19,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.test.context.event.annotation.BeforeTestMethod; +import org.springframework.web.multipart.MultipartFile; import com.ajou.hertz.common.file.dto.FileDto; import com.ajou.hertz.common.file.service.FileService; @@ -35,6 +36,7 @@ import com.ajou.hertz.domain.user.exception.UserPhoneDuplicationException; import com.ajou.hertz.domain.user.repository.UserRepository; import com.ajou.hertz.domain.user.service.UserCommandService; +import com.ajou.hertz.domain.user.service.UserProfileImageCommandService; import com.ajou.hertz.domain.user.service.UserQueryService; import com.ajou.hertz.util.ReflectionUtils; @@ -60,6 +62,9 @@ class UserCommandServiceTest { @Mock private FileService fileService; + @Mock + private UserProfileImageCommandService userProfileImageCommandService; + @BeforeTestMethod public void setUp() { given(hertzProperties.userDefaultProfileImageUrl()).willReturn("https://user-default-profile-image"); @@ -177,84 +182,23 @@ static Stream testDataForCreateNewUserWithKakao() throws Exception { assertThat(t).isInstanceOf(UserKakaoUidDuplicationException.class); } - // @Test - // void 주어진_id와_새_프로필_이미지로_프로필_이미지를_변경하고_이전_프로필_이미지는_삭제한다() throws Exception { - // Long userId = 1L; - // MockMultipartFile newProfileImage = new MockMultipartFile( - // "profileImage", - // "test.jpg", - // "image/jpeg", - // "test".getBytes() - // ); - // User user = createUser(userId, "password", null, Gender.MALE); - // given(userQueryService.getById(userId)).willReturn(user); - // given(fileService.uploadFile(any(), anyString())).willReturn( - // FileDto.create("new_profile_image.jpg", "new_profile_image.jpg", - // "https://example.com/user-profile-images")); - // - // // When - // UserDto updatedUser = sut.updateProfileImageUrl(userId, newProfileImage); - // - // // Then - // then(userQueryService).should().getById(userId); - // then(fileService).should().uploadFile(eq(newProfileImage), anyString()); - // then(fileService).should().deleteFile(anyString()); - // verifyEveryMocksShouldHaveNoMoreInteractions(); - // assertThat(updatedUser).hasFieldOrPropertyWithValue("profileImageUrl", user.getProfileImageUrl()); - // } - // - // @Test - // void 주어진_유저_ID와_새로운_프로필_이미지로_기존의_프로필_이미지를_변경한다_존재하지_않는_유저라면_예외가_발생한다() throws Exception { - // // given - // Long userId = 1L; - // MockMultipartFile newProfileImage = new MockMultipartFile( - // "profileImage", - // "test.jpg", - // "image/jpeg", - // "test".getBytes() - // ); - // given(userQueryService.getById(userId)).willThrow(UserNotFoundByIdException.class); - // - // // when - // Throwable t = catchThrowable(() -> sut.updateProfileImageUrl(userId, newProfileImage)); - // - // // then - // then(userQueryService).should().getById(userId); - // verifyEveryMocksShouldHaveNoMoreInteractions(); - // assertThat(t).isInstanceOf(UserNotFoundByIdException.class); - // } - @Test - void 주어진_유저_ID와_저장된_이미지로_프로필_이미지_URL을_변경한다() throws Exception { - // given + void 프로필_이미지_변경_시_uploadProfileImage가_잘_호출되는지_확인한다() throws Exception { + // Given Long userId = 1L; - User user = createUser(userId, "$2a$abc123", "12345"); - FileDto uploadedFile = createFileDto(); - given(userQueryService.getById(userId)).willReturn(user); + MultipartFile profileImage = org.mockito.Mockito.mock(MultipartFile.class); + User testUser = createUser(userId, "password", "kakaoUid"); + UserDto expectedUserDto = UserDto.from(testUser); + given(userProfileImageCommandService.updateProfileImage(userId, profileImage)).willReturn(expectedUserDto); - // when - UserDto updatedUserDto = sut.updateProfileImage(userId, uploadedFile); + // When + UserDto result = sut.updateUserProfileImage(userId, profileImage); - // then - then(userQueryService).should().getById(userId); - verifyEveryMocksShouldHaveNoMoreInteractions(); - assertThat(updatedUserDto.getProfileImageUrl()).isEqualTo(uploadedFile.getUrl()); - } + // Then + then(userProfileImageCommandService).should().updateProfileImage(userId, profileImage); + assertThat(result).isEqualTo(expectedUserDto); - @Test - void 주어진_유저_ID와_저장된_이미지로_프로필_이미지를_변경한다_존재하지_않는_유저라면_예외가_발생한다() throws Exception { - // given - Long userId = 1L; - FileDto uploadedFile = createFileDto(); - given(userQueryService.getById(userId)).willThrow(UserNotFoundByIdException.class); - - // when - Throwable t = catchThrowable(() -> sut.updateProfileImage(userId, uploadedFile)); - - // then - then(userQueryService).should().getById(userId); verifyEveryMocksShouldHaveNoMoreInteractions(); - assertThat(t).isInstanceOf(UserNotFoundByIdException.class); } @Test @@ -295,6 +239,7 @@ private void verifyEveryMocksShouldHaveNoMoreInteractions() { then(userRepository).shouldHaveNoMoreInteractions(); then(passwordEncoder).shouldHaveNoMoreInteractions(); then(fileService).shouldHaveNoMoreInteractions(); + then(userProfileImageCommandService).shouldHaveNoMoreInteractions(); } private static User createUser(Long id, String password, String kakaoUid, Gender gender) throws Exception { diff --git a/src/test/java/com/ajou/hertz/unit/domain/user/service/UserProfileImageCommandServiceTest.java b/src/test/java/com/ajou/hertz/unit/domain/user/service/UserProfileImageCommandServiceTest.java index 43e4cdc..e800f9b 100644 --- a/src/test/java/com/ajou/hertz/unit/domain/user/service/UserProfileImageCommandServiceTest.java +++ b/src/test/java/com/ajou/hertz/unit/domain/user/service/UserProfileImageCommandServiceTest.java @@ -1,5 +1,6 @@ package com.ajou.hertz.unit.domain.user.service; +import static org.assertj.core.api.Assertions.*; import static org.mockito.BDDMockito.*; import java.time.LocalDate; @@ -24,6 +25,7 @@ import com.ajou.hertz.domain.user.constant.RoleType; import com.ajou.hertz.domain.user.entity.User; import com.ajou.hertz.domain.user.entity.UserProfileImage; +import com.ajou.hertz.domain.user.exception.UserNotFoundByIdException; import com.ajou.hertz.domain.user.repository.UserProfileImageRepository; import com.ajou.hertz.domain.user.repository.UserRepository; import com.ajou.hertz.domain.user.service.UserProfileImageCommandService; @@ -78,7 +80,7 @@ public void setUp() { given(fileService.uploadFile(newProfileImage, uploadPath)).willReturn(uploadedFile); // when - sut.uploadProfileImage(userId, newProfileImage); + sut.updateProfileImage(userId, newProfileImage); // then then(userQueryService).should().getById(userId); @@ -88,7 +90,7 @@ public void setUp() { } @Test - void 기존_프로필_이미지가_존재하는_경우_이미지를_삭제하고_다시_이미지를_저장한다() throws Exception { + void 기존에_프로필_이미지가_존재하는_경우_이미지를_삭제하고_다시_이미지를_저장한다() throws Exception { // given Long userId = 1L; User user = createUser(userId, "password", "kakaoUid"); @@ -108,21 +110,43 @@ public void setUp() { String uploadPath = "user-profile-images/"; FileDto uploadedFile = createFileDto(); - given(fileService.uploadFile(newProfileImage, uploadPath)).willReturn(uploadedFile); + given(fileService.uploadFile(eq(newProfileImage), eq(uploadPath))).willReturn(uploadedFile); // when - sut.uploadProfileImage(userId, newProfileImage); + sut.updateProfileImage(userId, newProfileImage); // then then(userQueryService).should().getById(userId); then(userProfileImageRepository).should().findById(userId); - then(userProfileImageRepository).should().delete(oldProfileImage); - then(fileService).should().deleteFile(oldProfileImage.getStoredName()); - then(fileService).should().uploadFile(newProfileImage, uploadPath); + then(userProfileImageRepository).should().delete(any(UserProfileImage.class)); + then(fileService).should().deleteFile(anyString()); + then(fileService).should().uploadFile(eq(newProfileImage), eq(uploadPath)); then(userProfileImageRepository).should().save(any(UserProfileImage.class)); verifyEveryMocksShouldHaveNoMoreInteractions(); } + @Test + void 프로필_이미지_변경_시_존재하지_않는_유저라면_예외를_반환한다() throws Exception { + // given + Long userId = 1L; + given(userQueryService.getById(userId)).willThrow(UserNotFoundByIdException.class); + + MultipartFile newProfileImage = new MockMultipartFile( + "profileImage", + "newProfile.jpg", + "image/jpeg", + "new image content".getBytes() + ); + + // when + Throwable t = catchThrowable(() -> sut.updateProfileImage(userId, newProfileImage)); + + // then + assertThat(t).isInstanceOf(UserNotFoundByIdException.class); + then(userQueryService).should().getById(userId); + verifyEveryMocksShouldHaveNoMoreInteractions(); + } + private void verifyEveryMocksShouldHaveNoMoreInteractions() { then(userQueryService).shouldHaveNoMoreInteractions(); then(userRepository).shouldHaveNoMoreInteractions();