Skip to content

Commit

Permalink
Merge pull request #42 from Ajou-Hertz/feature/#23-find-user-email
Browse files Browse the repository at this point in the history
이메일 찾기 API 구현
  • Loading branch information
tinon1004 authored Feb 22, 2024
2 parents d6c4ec4 + 1ee7a4e commit 73d4d3a
Show file tree
Hide file tree
Showing 10 changed files with 151 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,11 @@ public class SecurityConfig {
};

private static final Map<String, HttpMethod> AUTH_WHITE_LIST = Map.of(
"/v*/auth/login", POST,
"/v*/auth/kakao/login", POST,
"/v*/users", POST,
"/v*/users/existence", GET,
"/v*/auth/login", POST,
"/v*/auth/kakao/login", POST
"/v*/users/email", GET
);

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public enum CustomExceptionType {
USER_PHONE_DUPLICATION(2203, "이미 사용 중인 전화번호입니다."),
USER_KAKAO_UID_DUPLICATION(2204, "이미 가입한 계정입니다."),
USER_NOT_FOUND_BY_KAKAO_UID(2205, "일치하는 회원을 찾을 수 없습니다."),
USER_NOT_FOUND_BY_PHONE(2206, "일치하는 회원을 찾을 수 없습니다."),

KAKAO_CLIENT(10000, "카카오 서버와의 통신 중 오류가 발생했습니다."),
;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.ajou.hertz.common.validator;

import java.lang.annotation.ElementType;
import static java.lang.annotation.ElementType.*;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
Expand All @@ -15,7 +16,7 @@
* <p>
* {@code null} elements are considered valid.
*/
@Target(ElementType.FIELD)
@Target({FIELD, PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneNumberValidator.class)
public @interface PhoneNumber {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.ajou.hertz.common.validator.PhoneNumber;
import com.ajou.hertz.domain.user.dto.UserDto;
import com.ajou.hertz.domain.user.dto.request.SignUpRequest;
import com.ajou.hertz.domain.user.dto.response.UserEmailResponse;
import com.ajou.hertz.domain.user.dto.response.UserExistenceResponse;
import com.ajou.hertz.domain.user.dto.response.UserResponse;
import com.ajou.hertz.domain.user.service.UserCommandService;
Expand All @@ -28,6 +30,7 @@
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;

@Tag(name = "유저 관련 API")
Expand Down Expand Up @@ -55,6 +58,25 @@ public UserExistenceResponse getExistenceOfUserByEmailV1_1(
return new UserExistenceResponse(existence);
}

@Operation(
summary = "전화번호로 유저 이메일 찾기",
description = "특정 유저를 식별할 수 있는 정보(전화번호)를 받아 일치하는 유저의 이메일을 조회합니다."
)
@ApiResponses({
@ApiResponse(responseCode = "200"),
@ApiResponse(responseCode = "404", description = "[2206] 전화번호에 해당하는 유저를 찾을 수 없는 경우", content = @Content)
})
@GetMapping(value = "/email", headers = API_MINOR_VERSION_HEADER_NAME + "=" + 1)
public UserEmailResponse getUserEmailByPhoneV1_1(
@Parameter(
description = "이메일을 찾고자 하는 유저의 전화번호",
example = "01012345678"
) @RequestParam @NotBlank @PhoneNumber String phone
) {
UserDto userDto = userQueryService.getDtoByPhone(phone);
return new UserEmailResponse(userDto.getEmail());
}

@Operation(
summary = "회원 등록",
description = "회원을 등록합니다."
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.ajou.hertz.domain.user.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Getter
public class UserEmailResponse {

@Schema(description = "이메일", example = "[email protected]")
private String email;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.ajou.hertz.domain.user.exception;

import com.ajou.hertz.common.exception.NotFoundException;
import com.ajou.hertz.common.exception.constant.CustomExceptionType;

public class UserNotFoundByPhoneException extends NotFoundException {
public UserNotFoundByPhoneException(String phone) {
super(CustomExceptionType.USER_NOT_FOUND_BY_PHONE, phone);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ public interface UserRepository extends JpaRepository<User, Long> {

Optional<User> findByKakaoUid(String kakaoUid);

Optional<User> findByPhone(String phone);

boolean existsByEmail(String email);

boolean existsByKakaoUid(String kakaoUid);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.ajou.hertz.domain.user.entity.User;
import com.ajou.hertz.domain.user.exception.UserNotFoundByEmailException;
import com.ajou.hertz.domain.user.exception.UserNotFoundByIdException;
import com.ajou.hertz.domain.user.exception.UserNotFoundByPhoneException;
import com.ajou.hertz.domain.user.repository.UserRepository;

import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -68,6 +69,18 @@ public UserDto getDtoByEmail(String email) {
return UserDto.from(getByEmail(email));
}

/**
* 전화번호로 user 정보를 조회한다.
*
* @param phone 조회하고자 하는 user의 전화번호
* @return 조회한 유저 정보가 담긴 dto
* @throws UserNotFoundByPhoneException 일치하는 유저를 찾지 못한 경우
*/
public UserDto getDtoByPhone(String phone) {
User user = userRepository.findByPhone(phone).orElseThrow(() -> new UserNotFoundByPhoneException(phone));
return UserDto.from(user);
}

/**
* 전달된 email을 사용 중인 회원의 존재 여부를 조회한다.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,40 @@ public void securitySetUp() throws Exception {
verifyEveryMocksShouldHaveNoMoreInteractions();
}

@Test
void 전화번호가_주어지고_주어진_전화번호를_사용_중인_회원의_이메일을_조회한다() throws Exception {
// given
String phone = "01012345678";
UserDto expectedResult = createUserDto();
given(userQueryService.getDtoByPhone(phone)).willReturn(expectedResult);

// when & then
mvc.perform(
get("/v1/users/email")
.header(API_MINOR_VERSION_HEADER_NAME, 1)
.queryParam("phone", phone)
)
.andExpect(status().isOk())
.andExpect(jsonPath("email").value(expectedResult.getEmail()));
then(userQueryService).should().getDtoByPhone(phone);
verifyEveryMocksShouldHaveNoMoreInteractions();
}

@Test
void 전화번호가_주어지고_주어진_전화번호를_사용_중인_회원의_이메일을_조회한다_전달된_전화번호가_잘못된_형식인_경우_에러가_발생한다() throws Exception {
// given
String phone = "12345";

// when & then
mvc.perform(
get("/v1/users/email")
.header(API_MINOR_VERSION_HEADER_NAME, 1)
.queryParam("phone", phone)
)
.andExpect(status().isUnprocessableEntity());
verifyEveryMocksShouldHaveNoMoreInteractions();
}

@Test
void 주어진_회원_정보로_신규_회원을_등록한다() throws Exception {
// given
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.ajou.hertz.domain.user.entity.User;
import com.ajou.hertz.domain.user.exception.UserNotFoundByEmailException;
import com.ajou.hertz.domain.user.exception.UserNotFoundByIdException;
import com.ajou.hertz.domain.user.exception.UserNotFoundByPhoneException;
import com.ajou.hertz.domain.user.repository.UserRepository;
import com.ajou.hertz.domain.user.service.UserQueryService;

Expand All @@ -34,6 +35,25 @@ class UserQueryServiceTest {
@Mock
private UserRepository userRepository;

@Test
void 카카오_유저_ID가_주어지고_주어진_카카오_유저_ID로_유저를_조회하면_조회된_유저_정보가_담긴_Optional_DTO가_반환된다() throws Exception {
// given
String kakaoUid = "12345";
User expectedResult = createUser(1L, "[email protected]", kakaoUid, "01012345678");
given(userRepository.findByKakaoUid(kakaoUid)).willReturn(Optional.of(expectedResult));

// when
Optional<UserDto> actualResult = sut.findDtoByKakaoUid(kakaoUid);

// then
then(userRepository).should().findByKakaoUid(kakaoUid);
verifyEveryMocksShouldHaveNoMoreInteractions();
assertThat(actualResult).isNotEmpty();
assertThat(actualResult.get())
.hasFieldOrPropertyWithValue("id", expectedResult.getId())
.hasFieldOrPropertyWithValue("kakaoUid", expectedResult.getKakaoUid());
}

@Test
void 유저_id가_주어지고_주어진_id로_유저를_조회하면_조회된_유저_정보가_반환된다() throws Exception {
// given
Expand Down Expand Up @@ -70,7 +90,7 @@ class UserQueryServiceTest {
void 이메일이_주어지고_주어진_이메일로_유저를_조회하면_조회된_유저_정보가_반환된다() throws Exception {
// given
String email = "[email protected]";
User expectedResult = createUser(1L, email, "1234");
User expectedResult = createUser(1L, email, "1234", "01012345678");
given(userRepository.findByEmail(email)).willReturn(Optional.of(expectedResult));

// when
Expand Down Expand Up @@ -100,22 +120,36 @@ class UserQueryServiceTest {
}

@Test
void 카카오_유저_ID가_주어지고_주어진_카카오_유저_ID로_유저를_조회하면_조회된_Optional_유저_정보가_반환된다() throws Exception {
void 전화번호가_주어지고_주어진_전화번호로_유저를_조회하면_조회된_유저_정보가_반환된다() throws Exception {
// given
String kakaoUid = "12345";
User expectedResult = createUser(1L, "[email protected]", kakaoUid);
given(userRepository.findByKakaoUid(kakaoUid)).willReturn(Optional.of(expectedResult));
String phone = "01012345678";
User expectedResult = createUser(1L, "[email protected]", "1234", phone);
given(userRepository.findByPhone(phone)).willReturn(Optional.of(expectedResult));

// when
Optional<UserDto> actualResult = sut.findDtoByKakaoUid(kakaoUid);
UserDto actualResult = sut.getDtoByPhone(phone);

// then
then(userRepository).should().findByKakaoUid(kakaoUid);
then(userRepository).should().findByPhone(phone);
verifyEveryMocksShouldHaveNoMoreInteractions();
assertThat(actualResult).isNotEmpty();
assertThat(actualResult.get())
assertThat(actualResult)
.hasFieldOrPropertyWithValue("id", expectedResult.getId())
.hasFieldOrPropertyWithValue("kakaoUid", expectedResult.getKakaoUid());
.hasFieldOrPropertyWithValue("phone", expectedResult.getPhone());
}

@Test
void 전화번호가_주어지고_주어진_전화번호로_유저를_조회한다_만약_일치하는_유저가_없다면_예외가_발생한다() {
// given
String phone = "01012345678";
given(userRepository.findByPhone(phone)).willReturn(Optional.empty());

// when
Throwable t = catchThrowable(() -> sut.getDtoByPhone(phone));

// then
then(userRepository).should().findByPhone(phone);
verifyEveryMocksShouldHaveNoMoreInteractions();
assertThat(t).isInstanceOf(UserNotFoundByPhoneException.class);
}

@Test
Expand Down Expand Up @@ -170,7 +204,7 @@ private void verifyEveryMocksShouldHaveNoMoreInteractions() {
then(userRepository).shouldHaveNoMoreInteractions();
}

private User createUser(Long id, String email, String kakaoUid) throws Exception {
private User createUser(Long id, String email, String kakaoUid, String phone) throws Exception {
Constructor<User> userConstructor = User.class.getDeclaredConstructor(
Long.class, Set.class, String.class, String.class, String.class,
String.class, LocalDate.class, Gender.class, String.class, String.class
Expand All @@ -185,12 +219,12 @@ private User createUser(Long id, String email, String kakaoUid) throws Exception
"https://user-default-profile-image-url",
LocalDate.of(2024, 1, 1),
Gender.ETC,
"01012345678",
phone,
null
);
}

private User createUser(Long id) throws Exception {
return createUser(id, "[email protected]", "12345");
return createUser(id, "[email protected]", "12345", "01012345678");
}
}

0 comments on commit 73d4d3a

Please sign in to comment.