From 6de235e41a6bdf3982267c65a0ae39840b3bfb61 Mon Sep 17 00:00:00 2001 From: dungbik Date: Wed, 16 Jul 2025 14:49:54 +0900 Subject: [PATCH 1/5] =?UTF-8?q?Feat:=20=ED=9A=8C=EC=9B=90=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=88=98=EC=A0=95=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/security/jwt/JwtComponent.java | 1 + .../flipnote/common/util/StringUtil.java | 12 +++++++++ .../user/controller/UserController.java | 12 +++++++++ .../project/flipnote/user/entity/User.java | 7 ++++++ .../user/model/UserRegisterRequest.java | 3 ++- .../user/model/UserUpdateRequest.java | 25 +++++++++++++++++++ .../user/model/UserUpdateResponse.java | 18 +++++++++++++ .../flipnote/user/service/UserService.java | 14 +++++++++++ 8 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 src/main/java/project/flipnote/common/util/StringUtil.java create mode 100644 src/main/java/project/flipnote/user/model/UserUpdateRequest.java create mode 100644 src/main/java/project/flipnote/user/model/UserUpdateResponse.java diff --git a/src/main/java/project/flipnote/common/security/jwt/JwtComponent.java b/src/main/java/project/flipnote/common/security/jwt/JwtComponent.java index d966861c..41bd9b07 100644 --- a/src/main/java/project/flipnote/common/security/jwt/JwtComponent.java +++ b/src/main/java/project/flipnote/common/security/jwt/JwtComponent.java @@ -60,6 +60,7 @@ private String generateToken(User user, Date expiration) { .subject(user.getEmail()) .id(String.valueOf(user.getId())) .claim(JwtConstants.ROLE, user.getRole().name()) + .claim(JwtConstants.TOKEN_VERSION, user.getTokenVersion()) .issuedAt(now) .expiration(expiration) .signWith(secretKey, Jwts.SIG.HS256) diff --git a/src/main/java/project/flipnote/common/util/StringUtil.java b/src/main/java/project/flipnote/common/util/StringUtil.java new file mode 100644 index 00000000..fb3e81c2 --- /dev/null +++ b/src/main/java/project/flipnote/common/util/StringUtil.java @@ -0,0 +1,12 @@ +package project.flipnote.common.util; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class StringUtil { + + public static String cleanPhone(String phone) { + return phone == null ? null : phone.replaceAll("-", ""); + } +} diff --git a/src/main/java/project/flipnote/user/controller/UserController.java b/src/main/java/project/flipnote/user/controller/UserController.java index 4a62aef8..ab489245 100644 --- a/src/main/java/project/flipnote/user/controller/UserController.java +++ b/src/main/java/project/flipnote/user/controller/UserController.java @@ -5,6 +5,7 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -14,6 +15,8 @@ import project.flipnote.common.security.dto.UserAuth; import project.flipnote.user.model.UserRegisterRequest; import project.flipnote.user.model.UserRegisterResponse; +import project.flipnote.user.model.UserUpdateRequest; +import project.flipnote.user.model.UserUpdateResponse; import project.flipnote.user.service.UserService; @RequiredArgsConstructor @@ -34,4 +37,13 @@ public ResponseEntity unregister(@AuthenticationPrincipal UserAuth userAut userService.unregister(userAuth.userId()); return ResponseEntity.noContent().build(); } + + @PutMapping + public ResponseEntity update( + @AuthenticationPrincipal UserAuth userAuth, + @Valid @RequestBody UserUpdateRequest req + ) { + UserUpdateResponse res = userService.update(userAuth.userId(), req); + return ResponseEntity.ok(res); + } } diff --git a/src/main/java/project/flipnote/user/entity/User.java b/src/main/java/project/flipnote/user/entity/User.java index 2fb95cc7..30ba719a 100644 --- a/src/main/java/project/flipnote/user/entity/User.java +++ b/src/main/java/project/flipnote/user/entity/User.java @@ -91,4 +91,11 @@ public void unregister() { public void increaseTokenVersion() { this.tokenVersion++; } + + public void update(String nickname, String phone, boolean smsAgree, String profileImageUrl) { + this.nickname = nickname; + this.phone = phone; + this.smsAgree = smsAgree; + this.profileImageUrl = profileImageUrl; + } } diff --git a/src/main/java/project/flipnote/user/model/UserRegisterRequest.java b/src/main/java/project/flipnote/user/model/UserRegisterRequest.java index 24e1cac5..e9c74b36 100644 --- a/src/main/java/project/flipnote/user/model/UserRegisterRequest.java +++ b/src/main/java/project/flipnote/user/model/UserRegisterRequest.java @@ -3,6 +3,7 @@ import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; +import project.flipnote.common.util.StringUtil; import project.flipnote.common.validation.annotation.ValidPassword; import project.flipnote.common.validation.annotation.ValidPhone; @@ -29,6 +30,6 @@ public record UserRegisterRequest( ) { public String getCleanedPhone() { - return phone == null ? null : phone.replaceAll("-", ""); + return StringUtil.cleanPhone(phone); } } diff --git a/src/main/java/project/flipnote/user/model/UserUpdateRequest.java b/src/main/java/project/flipnote/user/model/UserUpdateRequest.java new file mode 100644 index 00000000..b289f50a --- /dev/null +++ b/src/main/java/project/flipnote/user/model/UserUpdateRequest.java @@ -0,0 +1,25 @@ +package project.flipnote.user.model; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import project.flipnote.common.util.StringUtil; +import project.flipnote.common.validation.annotation.ValidPhone; + +public record UserUpdateRequest( + + @NotEmpty + String nickname, + + @ValidPhone + String phone, + + @NotNull + Boolean smsAgree, + + String profileImageUrl +) { + + public String getCleanedPhone() { + return StringUtil.cleanPhone(phone); + } +} diff --git a/src/main/java/project/flipnote/user/model/UserUpdateResponse.java b/src/main/java/project/flipnote/user/model/UserUpdateResponse.java new file mode 100644 index 00000000..be29cdc4 --- /dev/null +++ b/src/main/java/project/flipnote/user/model/UserUpdateResponse.java @@ -0,0 +1,18 @@ +package project.flipnote.user.model; + +import project.flipnote.user.entity.User; + +public record UserUpdateResponse( + Long userId, + String nickname, + String phone, + Boolean smsAgree, + String profileImageUrl +) { + + public static UserUpdateResponse from(User user) { + return new UserUpdateResponse( + user.getId(), user.getNickname(), user.getPhone(), user.isSmsAgree(), user.getProfileImageUrl() + ); + } +} diff --git a/src/main/java/project/flipnote/user/service/UserService.java b/src/main/java/project/flipnote/user/service/UserService.java index 9f1b63fd..20345032 100644 --- a/src/main/java/project/flipnote/user/service/UserService.java +++ b/src/main/java/project/flipnote/user/service/UserService.java @@ -15,6 +15,8 @@ import project.flipnote.user.exception.UserErrorCode; import project.flipnote.user.model.UserRegisterRequest; import project.flipnote.user.model.UserRegisterResponse; +import project.flipnote.user.model.UserUpdateRequest; +import project.flipnote.user.model.UserUpdateResponse; import project.flipnote.user.repository.UserRepository; @RequiredArgsConstructor @@ -61,6 +63,18 @@ public void unregister(Long userId) { tokenVersionRedisRepository.deleteTokenVersion(userId); } + @Transactional + public UserUpdateResponse update(Long userId, UserUpdateRequest req) { + User user = findActiveUserById(userId); + + String phone = req.getCleanedPhone(); + validatePhoneDuplicate(phone); + + user.update(req.nickname(), phone, req.smsAgree(), req.profileImageUrl()); + + return UserUpdateResponse.from(user); + } + private User findActiveUserById(Long userId) { return userRepository.findByIdAndStatus(userId, UserStatus.ACTIVE) .orElseThrow(() -> new BizException(UserErrorCode.USER_NOT_FOUND)); From f0f86adc47c071a62b43888e0e0255666b8ed6a4 Mon Sep 17 00:00:00 2001 From: dungbik Date: Wed, 16 Jul 2025 14:50:21 +0900 Subject: [PATCH 2/5] =?UTF-8?q?Feat:=20=ED=9A=8C=EC=9B=90=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=88=98=EC=A0=95=20=EB=8B=A8=EC=9C=84=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/service/UserServiceTest.java | 63 ++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/src/test/java/project/flipnote/user/service/UserServiceTest.java b/src/test/java/project/flipnote/user/service/UserServiceTest.java index d64ece9c..749c3c95 100644 --- a/src/test/java/project/flipnote/user/service/UserServiceTest.java +++ b/src/test/java/project/flipnote/user/service/UserServiceTest.java @@ -6,7 +6,6 @@ import java.util.Optional; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -26,6 +25,8 @@ import project.flipnote.user.exception.UserErrorCode; import project.flipnote.user.model.UserRegisterRequest; import project.flipnote.user.model.UserRegisterResponse; +import project.flipnote.user.model.UserUpdateRequest; +import project.flipnote.user.model.UserUpdateResponse; import project.flipnote.user.repository.UserRepository; @DisplayName("회원 서비스 단위 테스트") @@ -172,4 +173,64 @@ void fail_userNotFound() { assertThat(exception.getErrorCode()).isEqualTo(UserErrorCode.USER_NOT_FOUND); } } + + @DisplayName("회원 정보 수정 테스트") + @Nested + class Update { + + @DisplayName("성공") + @Test + void success() { + User user = UserFixture.createActiveUser(); + UserUpdateRequest req = new UserUpdateRequest( + "새로운닉네임", "010-9876-5432", true, "new/image.jpg" + ); + String cleanedPhone = req.getCleanedPhone(); + + given(userRepository.findByIdAndStatus(user.getId(), UserStatus.ACTIVE)).willReturn(Optional.of(user)); + given(userRepository.existsByPhone(cleanedPhone)).willReturn(false); + + UserUpdateResponse res = userService.update(user.getId(), req); + + assertThat(res.userId()).isEqualTo(user.getId()); + assertThat(res.nickname()).isEqualTo(req.nickname()); + assertThat(res.phone()).isEqualTo(cleanedPhone); + assertThat(res.smsAgree()).isEqualTo(req.smsAgree()); + assertThat(res.profileImageUrl()).isEqualTo(req.profileImageUrl()); + + verify(userRepository, times(1)).findByIdAndStatus(anyLong(), any(UserStatus.class)); + verify(userRepository, times(1)).existsByPhone(anyString()); + } + + @DisplayName("존재하지 않는 회원 수정 시 예외 발생") + @Test + void fail_userNotFound() { + UserUpdateRequest req = new UserUpdateRequest( + "새로운닉네임", "010-9876-5432", true, "new/image.jpg" + ); + + given(userRepository.findByIdAndStatus(anyLong(), any(UserStatus.class))).willReturn(Optional.empty()); + + BizException exception = assertThrows(BizException.class, () -> userService.update(99L, req)); + + assertThat(exception.getErrorCode()).isEqualTo(UserErrorCode.USER_NOT_FOUND); + } + + @DisplayName("중복된 전화번호로 수정 시 예외 발생") + @Test + void fail_duplicatePhone() { + User user = UserFixture.createActiveUser(); + UserUpdateRequest req = new UserUpdateRequest( + "새로운닉네임", "010-9999-9999", true, "new/image.jpg" + ); + String duplicatePhone = req.getCleanedPhone(); + + given(userRepository.findByIdAndStatus(user.getId(), UserStatus.ACTIVE)).willReturn(Optional.of(user)); + given(userRepository.existsByPhone(duplicatePhone)).willReturn(true); + + BizException exception = assertThrows(BizException.class, () -> userService.update(user.getId(), req)); + + assertThat(exception.getErrorCode()).isEqualTo(UserErrorCode.DUPLICATE_PHONE); + } + } } From 2f6c428d3eddf02a72fcfdae38cd2605f1eb88bf Mon Sep 17 00:00:00 2001 From: dungbik Date: Wed, 16 Jul 2025 16:28:45 +0900 Subject: [PATCH 3/5] =?UTF-8?q?Fix:=20=EC=A0=84=ED=99=94=EB=B2=88=ED=98=B8?= =?UTF-8?q?=EB=A5=BC=20=EC=9C=A0=EC=A7=80=ED=95=9C=20=EC=83=81=ED=83=9C?= =?UTF-8?q?=EB=A1=9C=20=ED=9A=8C=EC=9B=90=20=EC=A0=95=EB=B3=B4=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=EC=8B=9C=20=EC=98=88=EC=99=B8=20=EB=B0=9C=EC=83=9D?= =?UTF-8?q?=ED=95=98=EB=8D=98=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/project/flipnote/user/service/UserService.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/project/flipnote/user/service/UserService.java b/src/main/java/project/flipnote/user/service/UserService.java index 20345032..debbc838 100644 --- a/src/main/java/project/flipnote/user/service/UserService.java +++ b/src/main/java/project/flipnote/user/service/UserService.java @@ -2,6 +2,7 @@ import java.util.Objects; +import org.apache.commons.lang3.StringUtils; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -68,7 +69,9 @@ public UserUpdateResponse update(Long userId, UserUpdateRequest req) { User user = findActiveUserById(userId); String phone = req.getCleanedPhone(); - validatePhoneDuplicate(phone); + if (!Objects.equals(user.getPhone(), phone)) { + validatePhoneDuplicate(phone); + } user.update(req.nickname(), phone, req.smsAgree(), req.profileImageUrl()); From 5fad078359ffa37a3651d5955a7460a80f10c82a Mon Sep 17 00:00:00 2001 From: dungbik Date: Wed, 16 Jul 2025 16:31:17 +0900 Subject: [PATCH 4/5] =?UTF-8?q?Test:=20=EC=A0=84=ED=99=94=EB=B2=88?= =?UTF-8?q?=ED=98=B8=EB=A5=BC=20=EC=9C=A0=EC=A7=80=ED=95=9C=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=EB=A1=9C=20=ED=9A=8C=EC=9B=90=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=EC=9D=84=20=ED=95=98=EB=8A=94=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/service/UserServiceTest.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/test/java/project/flipnote/user/service/UserServiceTest.java b/src/test/java/project/flipnote/user/service/UserServiceTest.java index 749c3c95..bada57d2 100644 --- a/src/test/java/project/flipnote/user/service/UserServiceTest.java +++ b/src/test/java/project/flipnote/user/service/UserServiceTest.java @@ -202,6 +202,28 @@ void success() { verify(userRepository, times(1)).existsByPhone(anyString()); } + @DisplayName("동일한 전화번호로 수정 시 성공") + @Test + void success_withSamePhone() { + User user = UserFixture.createActiveUser(); + UserUpdateRequest req = new UserUpdateRequest( + "새로운닉네임", user.getPhone(), true, "new/image.jpg" + ); + String cleanedPhone = req.getCleanedPhone(); + + given(userRepository.findByIdAndStatus(user.getId(), UserStatus.ACTIVE)).willReturn(Optional.of(user)); + + UserUpdateResponse res = userService.update(user.getId(), req); + + assertThat(res.userId()).isEqualTo(user.getId()); + assertThat(res.nickname()).isEqualTo(req.nickname()); + assertThat(res.phone()).isEqualTo(cleanedPhone); + assertThat(res.smsAgree()).isEqualTo(req.smsAgree()); + assertThat(res.profileImageUrl()).isEqualTo(req.profileImageUrl()); + + verify(userRepository, never()).existsByPhone(anyString()); + } + @DisplayName("존재하지 않는 회원 수정 시 예외 발생") @Test void fail_userNotFound() { From 64baf8e164208f9dad8b220cea8a41ca4a63ff37 Mon Sep 17 00:00:00 2001 From: dungbik Date: Wed, 16 Jul 2025 17:04:38 +0900 Subject: [PATCH 5/5] =?UTF-8?q?Feat:=20=EC=A0=84=ED=99=94=EB=B2=88?= =?UTF-8?q?=ED=98=B8=EB=A5=BC=20E.164=20=ED=91=9C=EC=A4=80=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=A0=80=EC=9E=A5=EB=90=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + .../flipnote/common/util/PhoneUtil.java | 27 +++++++ .../flipnote/common/util/StringUtil.java | 12 ---- .../validator/PhoneConstraintValidator.java | 13 +++- .../user/model/UserRegisterRequest.java | 6 +- .../user/model/UserUpdateRequest.java | 6 +- .../flipnote/user/service/UserService.java | 5 +- .../PhoneConstraintValidatorTest.java | 71 ------------------- .../user/service/UserServiceTest.java | 12 ++-- 9 files changed, 53 insertions(+), 100 deletions(-) create mode 100644 src/main/java/project/flipnote/common/util/PhoneUtil.java delete mode 100644 src/main/java/project/flipnote/common/util/StringUtil.java delete mode 100644 src/test/java/project/flipnote/common/validator/PhoneConstraintValidatorTest.java diff --git a/build.gradle b/build.gradle index bad173c4..ea4663fa 100644 --- a/build.gradle +++ b/build.gradle @@ -36,6 +36,7 @@ dependencies { implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.9' implementation 'io.jsonwebtoken:jjwt:0.12.6' implementation 'com.resend:resend-java:4.1.1' + implementation 'com.googlecode.libphonenumber:libphonenumber:9.0.9' compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' diff --git a/src/main/java/project/flipnote/common/util/PhoneUtil.java b/src/main/java/project/flipnote/common/util/PhoneUtil.java new file mode 100644 index 00000000..3e6158b2 --- /dev/null +++ b/src/main/java/project/flipnote/common/util/PhoneUtil.java @@ -0,0 +1,27 @@ +package project.flipnote.common.util; + +import com.google.i18n.phonenumbers.NumberParseException; +import com.google.i18n.phonenumbers.PhoneNumberUtil; +import com.google.i18n.phonenumbers.Phonenumber; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class PhoneUtil { + + private static final PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance(); + + public static String normalize(String phone) { + if (phone == null) { + return null; + } + + try { + Phonenumber.PhoneNumber phoneNumber = phoneUtil.parse(phone, "KR"); + return phoneUtil.format(phoneNumber, PhoneNumberUtil.PhoneNumberFormat.E164); + } catch (NumberParseException e) { + throw new IllegalStateException("전화번호 정규화에 실패하였습니다. phone: " + phone, e); + } + } +} diff --git a/src/main/java/project/flipnote/common/util/StringUtil.java b/src/main/java/project/flipnote/common/util/StringUtil.java deleted file mode 100644 index fb3e81c2..00000000 --- a/src/main/java/project/flipnote/common/util/StringUtil.java +++ /dev/null @@ -1,12 +0,0 @@ -package project.flipnote.common.util; - -import lombok.AccessLevel; -import lombok.NoArgsConstructor; - -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public class StringUtil { - - public static String cleanPhone(String phone) { - return phone == null ? null : phone.replaceAll("-", ""); - } -} diff --git a/src/main/java/project/flipnote/common/validation/validator/PhoneConstraintValidator.java b/src/main/java/project/flipnote/common/validation/validator/PhoneConstraintValidator.java index 636d294e..43f01845 100644 --- a/src/main/java/project/flipnote/common/validation/validator/PhoneConstraintValidator.java +++ b/src/main/java/project/flipnote/common/validation/validator/PhoneConstraintValidator.java @@ -2,13 +2,17 @@ import java.util.Objects; +import com.google.i18n.phonenumbers.NumberParseException; +import com.google.i18n.phonenumbers.PhoneNumberUtil; +import com.google.i18n.phonenumbers.Phonenumber; + import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; import project.flipnote.common.validation.annotation.ValidPhone; public class PhoneConstraintValidator implements ConstraintValidator { - private static final String PHONE_PATTERN = "^010-\\d{4}-\\d{4}$"; + private final PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance(); @Override public boolean isValid(String phone, ConstraintValidatorContext context) { @@ -16,6 +20,11 @@ public boolean isValid(String phone, ConstraintValidatorContext context) { return true; } - return phone.matches(PHONE_PATTERN); + try { + Phonenumber.PhoneNumber number = phoneUtil.parse(phone, "KR"); + return phoneUtil.isValidNumber(number); + } catch (NumberParseException e) { + return false; + } } } diff --git a/src/main/java/project/flipnote/user/model/UserRegisterRequest.java b/src/main/java/project/flipnote/user/model/UserRegisterRequest.java index e9c74b36..4e175c24 100644 --- a/src/main/java/project/flipnote/user/model/UserRegisterRequest.java +++ b/src/main/java/project/flipnote/user/model/UserRegisterRequest.java @@ -3,7 +3,7 @@ import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; -import project.flipnote.common.util.StringUtil; +import project.flipnote.common.util.PhoneUtil; import project.flipnote.common.validation.annotation.ValidPassword; import project.flipnote.common.validation.annotation.ValidPhone; @@ -29,7 +29,7 @@ public record UserRegisterRequest( String profileImageUrl ) { - public String getCleanedPhone() { - return StringUtil.cleanPhone(phone); + public String getNormalizedPhone() { + return PhoneUtil.normalize(phone); } } diff --git a/src/main/java/project/flipnote/user/model/UserUpdateRequest.java b/src/main/java/project/flipnote/user/model/UserUpdateRequest.java index b289f50a..a12374a9 100644 --- a/src/main/java/project/flipnote/user/model/UserUpdateRequest.java +++ b/src/main/java/project/flipnote/user/model/UserUpdateRequest.java @@ -2,7 +2,7 @@ import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; -import project.flipnote.common.util.StringUtil; +import project.flipnote.common.util.PhoneUtil; import project.flipnote.common.validation.annotation.ValidPhone; public record UserUpdateRequest( @@ -19,7 +19,7 @@ public record UserUpdateRequest( String profileImageUrl ) { - public String getCleanedPhone() { - return StringUtil.cleanPhone(phone); + public String getNormalizedPhone() { + return PhoneUtil.normalize(phone); } } diff --git a/src/main/java/project/flipnote/user/service/UserService.java b/src/main/java/project/flipnote/user/service/UserService.java index debbc838..3186954a 100644 --- a/src/main/java/project/flipnote/user/service/UserService.java +++ b/src/main/java/project/flipnote/user/service/UserService.java @@ -2,7 +2,6 @@ import java.util.Objects; -import org.apache.commons.lang3.StringUtils; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -33,7 +32,7 @@ public class UserService { @Transactional public UserRegisterResponse register(UserRegisterRequest req) { String email = req.email(); - String phone = req.getCleanedPhone(); + String phone = req.getNormalizedPhone(); validateEmailDuplicate(email); validatePhoneDuplicate(phone); @@ -68,7 +67,7 @@ public void unregister(Long userId) { public UserUpdateResponse update(Long userId, UserUpdateRequest req) { User user = findActiveUserById(userId); - String phone = req.getCleanedPhone(); + String phone = req.getNormalizedPhone(); if (!Objects.equals(user.getPhone(), phone)) { validatePhoneDuplicate(phone); } diff --git a/src/test/java/project/flipnote/common/validator/PhoneConstraintValidatorTest.java b/src/test/java/project/flipnote/common/validator/PhoneConstraintValidatorTest.java deleted file mode 100644 index c8a8ca34..00000000 --- a/src/test/java/project/flipnote/common/validator/PhoneConstraintValidatorTest.java +++ /dev/null @@ -1,71 +0,0 @@ -package project.flipnote.common.validator; - -import static org.assertj.core.api.Assertions.*; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import jakarta.validation.ConstraintValidatorContext; -import project.flipnote.common.validation.validator.PhoneConstraintValidator; - -@DisplayName("휴대폰 번호 유효성 검증기 단위 테스트") -@ExtendWith(MockitoExtension.class) -class PhoneConstraintValidatorTest { - - PhoneConstraintValidator validator = new PhoneConstraintValidator(); - - @Mock - ConstraintValidatorContext context; - - @Test - @DisplayName("010-1234-5678 형식은 true를 반환한다") - void validPhoneWithHyphens() { - assertThat(validator.isValid("010-1234-5678", context)).isTrue(); - assertThat(validator.isValid("010-4567-1238", context)).isTrue(); - } - - @Test - @DisplayName("null은 true를 반환한다") - void validPhoneNull() { - assertThat(validator.isValid(null, context)).isTrue(); - } - - @Test - @DisplayName("01012345678 형식은 false를 반환한다") - void validPhoneWithoutHyphens() { - assertThat(validator.isValid("01012345678", context)).isFalse(); - } - - @Test - @DisplayName("010-123-4567 등 잘못된 자리수는 false") - void invalidPhoneWrongDigits() { - assertThat(validator.isValid("010-123-4567", context)).isFalse(); - assertThat(validator.isValid("010-12345-6789", context)).isFalse(); - assertThat(validator.isValid("010-12345-678", context)).isFalse(); - assertThat(validator.isValid("010-12345-67890", context)).isFalse(); - } - - @Test - @DisplayName("010이 아닌 번호는 false") - void invalidPhoneNot010() { - assertThat(validator.isValid("011-1234-5678", context)).isFalse(); - assertThat(validator.isValid("019-1234-5678", context)).isFalse(); - } - - @Test - @DisplayName("숫자가 아닌 문자가 포함되면 false") - void invalidPhoneWithLetters() { - assertThat(validator.isValid("010-ABCD-5678", context)).isFalse(); - assertThat(validator.isValid("0101234abcd", context)).isFalse(); - } - - @Test - @DisplayName("빈 문자열은 false") - void invalidPhoneEmpty() { - assertThat(validator.isValid("", context)).isFalse(); - assertThat(validator.isValid(" ", context)).isFalse(); - } -} diff --git a/src/test/java/project/flipnote/user/service/UserServiceTest.java b/src/test/java/project/flipnote/user/service/UserServiceTest.java index bada57d2..0bded745 100644 --- a/src/test/java/project/flipnote/user/service/UserServiceTest.java +++ b/src/test/java/project/flipnote/user/service/UserServiceTest.java @@ -185,16 +185,16 @@ void success() { UserUpdateRequest req = new UserUpdateRequest( "새로운닉네임", "010-9876-5432", true, "new/image.jpg" ); - String cleanedPhone = req.getCleanedPhone(); + String normalizedPhone = req.getNormalizedPhone(); given(userRepository.findByIdAndStatus(user.getId(), UserStatus.ACTIVE)).willReturn(Optional.of(user)); - given(userRepository.existsByPhone(cleanedPhone)).willReturn(false); + given(userRepository.existsByPhone(normalizedPhone)).willReturn(false); UserUpdateResponse res = userService.update(user.getId(), req); assertThat(res.userId()).isEqualTo(user.getId()); assertThat(res.nickname()).isEqualTo(req.nickname()); - assertThat(res.phone()).isEqualTo(cleanedPhone); + assertThat(res.phone()).isEqualTo(normalizedPhone); assertThat(res.smsAgree()).isEqualTo(req.smsAgree()); assertThat(res.profileImageUrl()).isEqualTo(req.profileImageUrl()); @@ -209,7 +209,7 @@ void success_withSamePhone() { UserUpdateRequest req = new UserUpdateRequest( "새로운닉네임", user.getPhone(), true, "new/image.jpg" ); - String cleanedPhone = req.getCleanedPhone(); + String normalizedPhone = req.getNormalizedPhone(); given(userRepository.findByIdAndStatus(user.getId(), UserStatus.ACTIVE)).willReturn(Optional.of(user)); @@ -217,7 +217,7 @@ void success_withSamePhone() { assertThat(res.userId()).isEqualTo(user.getId()); assertThat(res.nickname()).isEqualTo(req.nickname()); - assertThat(res.phone()).isEqualTo(cleanedPhone); + assertThat(res.phone()).isEqualTo(normalizedPhone); assertThat(res.smsAgree()).isEqualTo(req.smsAgree()); assertThat(res.profileImageUrl()).isEqualTo(req.profileImageUrl()); @@ -245,7 +245,7 @@ void fail_duplicatePhone() { UserUpdateRequest req = new UserUpdateRequest( "새로운닉네임", "010-9999-9999", true, "new/image.jpg" ); - String duplicatePhone = req.getCleanedPhone(); + String duplicatePhone = req.getNormalizedPhone(); given(userRepository.findByIdAndStatus(user.getId(), UserStatus.ACTIVE)).willReturn(Optional.of(user)); given(userRepository.existsByPhone(duplicatePhone)).willReturn(true);