Skip to content

Commit a508457

Browse files
authored
[Feat] 닉네임, 전화번호 중복 확인 API 구현
2 parents 1cf2244 + 0638931 commit a508457

File tree

10 files changed

+232
-10
lines changed

10 files changed

+232
-10
lines changed

src/main/java/timeeat/controller/member/MemberController.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
import jakarta.validation.Valid;
44
import lombok.RequiredArgsConstructor;
55
import org.springframework.http.ResponseEntity;
6+
import org.springframework.web.bind.annotation.GetMapping;
67
import org.springframework.web.bind.annotation.PutMapping;
78
import org.springframework.web.bind.annotation.RequestBody;
9+
import org.springframework.web.bind.annotation.RequestParam;
810
import org.springframework.web.bind.annotation.RestController;
911
import timeeat.controller.web.auth.LoginMember;
1012
import timeeat.service.service.MemberService;
@@ -15,6 +17,18 @@ public class MemberController {
1517

1618
private final MemberService memberService;
1719

20+
@GetMapping("/api/member/nickname/check")
21+
public ResponseEntity<Void> checkNickname(LoginMember member, @RequestParam String nickname) {
22+
memberService.validateNickname(nickname, member.id());
23+
return ResponseEntity.noContent().build();
24+
}
25+
26+
@GetMapping("/api/member/phone-number/check")
27+
public ResponseEntity<Void> checkPhoneNumber(LoginMember member, @RequestParam String phoneNumber) {
28+
memberService.validatePhoneNumber(phoneNumber, member.id());
29+
return ResponseEntity.noContent().build();
30+
}
31+
1832
@PutMapping("/api/member")
1933
public ResponseEntity<MemberResponse> updateMember(LoginMember member,
2034
@RequestBody @Valid MemberUpdateRequest request) {

src/main/java/timeeat/exception/EtcErrorCode.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ public enum EtcErrorCode {
1313
MEDIA_TYPE_NOT_SUPPORTED("CLIENT005", "허용되지 않은 미디어 타입입니다.", HttpStatus.UNSUPPORTED_MEDIA_TYPE),
1414
NO_RESOURCE_FOUND("CLIENT006", "요청한 리소스를 찾을 수 없습니다.", HttpStatus.NOT_FOUND),
1515
NO_COOKIE_FOUND("CLIENT007", "필수 쿠키 값이 존재하지 않습니다.", HttpStatus.BAD_REQUEST),
16-
NO_HEADER_FOUND("CLIENT007", "필수 헤더 값이 존재하지 않습니다.", HttpStatus.BAD_REQUEST),
16+
NO_HEADER_FOUND("CLIENT008", "필수 헤더 값이 존재하지 않습니다.", HttpStatus.BAD_REQUEST),
17+
NO_PARAMETER_FOUND("CLIENT009", "필수 파라미터 값이 존재하지 않습니다.", HttpStatus.BAD_REQUEST),
1718

1819
INTERNAL_SERVER_ERROR("SERVER001", "서버 내부 에러가 발생했습니다.", HttpStatus.INTERNAL_SERVER_ERROR),
1920
;

src/main/java/timeeat/exception/GlobalExceptionHandler.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.springframework.web.HttpRequestMethodNotSupportedException;
1010
import org.springframework.web.bind.MissingRequestCookieException;
1111
import org.springframework.web.bind.MissingRequestHeaderException;
12+
import org.springframework.web.bind.MissingServletRequestParameterException;
1213
import org.springframework.web.bind.annotation.ExceptionHandler;
1314
import org.springframework.web.bind.annotation.RestControllerAdvice;
1415
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
@@ -68,6 +69,12 @@ public ResponseEntity<ErrorResponse> handleMissingRequestHeaderException(Missing
6869
return toErrorResponse(EtcErrorCode.NO_HEADER_FOUND);
6970
}
7071

72+
@ExceptionHandler(MissingServletRequestParameterException.class)
73+
public ResponseEntity<ErrorResponse> handleMissingServletRequestParameterException(
74+
MissingServletRequestParameterException exception) {
75+
return toErrorResponse(EtcErrorCode.NO_PARAMETER_FOUND);
76+
}
77+
7178
@ExceptionHandler(BusinessException.class)
7279
public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException exception) {
7380
ErrorResponse response = new ErrorResponse(exception.getErrorCode());

src/main/java/timeeat/service/service/MemberService.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,18 @@ public class MemberService {
1616

1717
private final MemberRepository memberRepository;
1818

19+
@Transactional(readOnly = true)
20+
public void validateNickname(String nickname, long memberId) {
21+
Member member = memberRepository.getById(memberId);
22+
validateNicknameNotDuplicate(member, nickname);
23+
}
24+
25+
@Transactional(readOnly = true)
26+
public void validatePhoneNumber(String phoneNumber, long memberId) {
27+
Member member = memberRepository.getById(memberId);
28+
validatePhoneNumberNotDuplicate(member, phoneNumber);
29+
}
30+
1931
@Transactional
2032
public MemberResponse update(long memberId, MemberUpdateRequest request) {
2133
Member member = memberRepository.getById(memberId);

src/test/java/timeeat/controller/BaseControllerTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public class BaseControllerTest {
3535

3636
private static final OauthToken DEFAULT_OAUTH_TOKEN = new OauthToken("oauth-access-token");
3737
private static final OauthMemberInformation DEFAULT_OAUTH_MEMBER_INFO =
38-
new OauthMemberInformation(123L, "nickname");
38+
new OauthMemberInformation(314159248183772L, "nickname");
3939

4040
@LocalServerPort
4141
private int port;

src/test/java/timeeat/controller/member/MemberControllerTest.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,59 @@
1111

1212
class MemberControllerTest extends BaseControllerTest {
1313

14+
@Nested
15+
class CheckNickname {
16+
17+
@Test
18+
void 중복되지_않는_닉네임을_확인할_수_있다() {
19+
given()
20+
.header(HttpHeaders.AUTHORIZATION, accessToken())
21+
.queryParam("nickname", "new-nickname")
22+
.when().get("/api/member/nickname/check")
23+
.then()
24+
.statusCode(204);
25+
}
26+
27+
@Test
28+
void 중복된_닉네임을_확인할_수_있다() {
29+
String existingNickname = "existing-nickname";
30+
memberGenerator.generateRegisteredMember("123", existingNickname, "01012345678");
31+
32+
given()
33+
.header(HttpHeaders.AUTHORIZATION, accessToken())
34+
.queryParam("nickname", existingNickname)
35+
.when().get("/api/member/nickname/check")
36+
.then()
37+
.statusCode(400);
38+
}
39+
}
40+
41+
@Nested
42+
class CheckPhoneNumber {
43+
44+
@Test
45+
void 중복되지_않는_전화번호를_확인할_수_있다() {
46+
given()
47+
.header(HttpHeaders.AUTHORIZATION, accessToken())
48+
.queryParam("phoneNumber", "01098765432")
49+
.when().get("/api/member/phone-number/check")
50+
.then()
51+
.statusCode(204);
52+
}
53+
54+
@Test
55+
void 중복된_전화번호를_확인할_수_있다() {
56+
String existingPhoneNumber = "01012345678";
57+
memberGenerator.generateRegisteredMember("123", "nickname", existingPhoneNumber);
58+
given()
59+
.header(HttpHeaders.AUTHORIZATION, accessToken())
60+
.queryParam("phoneNumber", existingPhoneNumber)
61+
.when().get("/api/member/phone-number/check")
62+
.then()
63+
.statusCode(400);
64+
}
65+
}
66+
1467
@Nested
1568
class UpdateMember {
1669

src/test/java/timeeat/document/auth/AuthDocumentTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class RedirectOauthLoginPage {
4949
void Oauth_로그인_페이지로_리다이렉트_할_수_있다() throws URISyntaxException {
5050
doReturn(new URI("http://localhost:8080")).when(authService).getOauthLoginUrl(anyString());
5151

52-
var document = document("auth/oauth_redirect", 302)
52+
var document = document("auth/oauth-redirect", 302)
5353
.request(requestDocument)
5454
.response(responseDocument)
5555
.build();

src/test/java/timeeat/document/member/MemberDocumentTest.java

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
package timeeat.document.member;
22

33
import static org.mockito.ArgumentMatchers.anyLong;
4+
import static org.mockito.ArgumentMatchers.anyString;
45
import static org.mockito.ArgumentMatchers.eq;
6+
import static org.mockito.Mockito.doNothing;
57
import static org.mockito.Mockito.doReturn;
68
import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
79
import static org.springframework.restdocs.payload.JsonFieldType.BOOLEAN;
810
import static org.springframework.restdocs.payload.JsonFieldType.NUMBER;
911
import static org.springframework.restdocs.payload.JsonFieldType.STRING;
1012
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
13+
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
1114

1215
import io.restassured.http.ContentType;
1316
import org.junit.jupiter.api.Nested;
@@ -23,7 +26,65 @@
2326
public class MemberDocumentTest extends BaseDocumentTest {
2427

2528
@Nested
26-
class updateMember {
29+
class CheckNickname {
30+
31+
RestDocsRequest requestDocument = request()
32+
.tag(Tag.MEMBER_API)
33+
.summary("닉네임 중복 검사")
34+
.requestHeader(
35+
headerWithName(HttpHeaders.AUTHORIZATION).description("액세스 토큰")
36+
).queryParameter(
37+
parameterWithName("nickname").description("검사할 닉네임")
38+
);
39+
40+
@Test
41+
void 중복되지_않는_닉네임을_확인할_수_있다() {
42+
doNothing().when(memberService).validateNickname(anyString(), anyLong());
43+
44+
var document = document("member/nickname-check", 204)
45+
.request(requestDocument)
46+
.build();
47+
48+
given(document)
49+
.contentType(ContentType.JSON)
50+
.header(HttpHeaders.AUTHORIZATION, accessToken())
51+
.queryParam("nickname", "new-nickname")
52+
.when().get("/api/member/nickname/check")
53+
.then().statusCode(204);
54+
}
55+
}
56+
57+
@Nested
58+
class CheckPhoneNumber {
59+
60+
RestDocsRequest requestDocument = request()
61+
.tag(Tag.MEMBER_API)
62+
.summary("전화번호 중복 검사")
63+
.requestHeader(
64+
headerWithName(HttpHeaders.AUTHORIZATION).description("액세스 토큰")
65+
).queryParameter(
66+
parameterWithName("phoneNumber").description("검사할 전화번호 ex) 01012345678")
67+
);
68+
69+
@Test
70+
void 중복되지_않는_전화번호를_확인할_수_있다() {
71+
doNothing().when(memberService).validatePhoneNumber(anyString(), anyLong());
72+
73+
var document = document("member/phone-number-check", 204)
74+
.request(requestDocument)
75+
.build();
76+
77+
given(document)
78+
.contentType(ContentType.JSON)
79+
.header(HttpHeaders.AUTHORIZATION, accessToken())
80+
.queryParam("phoneNumber", "01098765432")
81+
.when().get("/api/member/phone-number/check")
82+
.then().statusCode(204);
83+
}
84+
}
85+
86+
@Nested
87+
class UpdateMember {
2788

2889
RestDocsRequest requestDocument = request()
2990
.tag(Tag.MEMBER_API)

src/test/java/timeeat/fixture/MemberGenerator.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22

33
import org.springframework.stereotype.Component;
44
import timeeat.domain.member.Member;
5+
import timeeat.enums.InterestArea;
56
import timeeat.repository.member.MemberRepository;
67

78
@Component
89
public class MemberGenerator {
910

11+
private static final String DEFAULT_INTEREST_AREA = InterestArea.SEONGBUK.getAreaName();
12+
1013
private final MemberRepository memberRepository;
1114

1215
public MemberGenerator(MemberRepository memberRepository) {
@@ -20,4 +23,8 @@ public Member generate(String socialId) {
2023
public Member generate(String socialId, String nickname) {
2124
return memberRepository.save(new Member(socialId, nickname));
2225
}
26+
27+
public Member generateRegisteredMember(String socialId, String nickname, String phoneNumber) {
28+
return memberRepository.save(new Member(socialId, nickname, phoneNumber, DEFAULT_INTEREST_AREA, true));
29+
}
2330
}

src/test/java/timeeat/service/service/MemberServiceTest.java

Lines changed: 73 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package timeeat.service.service;
22

33
import static org.assertj.core.api.Assertions.assertThat;
4+
import static org.assertj.core.api.Assertions.assertThatCode;
45
import static org.junit.jupiter.api.Assertions.assertAll;
56
import static org.junit.jupiter.api.Assertions.assertThrows;
67

@@ -19,6 +20,76 @@ class MemberServiceTest extends BaseServiceTest {
1920
@Autowired
2021
private MemberService memberService;
2122

23+
@Nested
24+
class ValidateNickname {
25+
26+
@Test
27+
void 중복되지_않은_닉네임이면_예외가_발생하지_않는다() {
28+
memberGenerator.generate("123", "nickname");
29+
Member member = memberGenerator.generate("456", "unique-nickname");
30+
String newNickname = "new-unique-nickname";
31+
32+
assertThatCode(() -> memberService.validateNickname(newNickname, member.getId()))
33+
.doesNotThrowAnyException();
34+
}
35+
36+
@Test
37+
void 자신의_기존_닉네임이면_예외가_발생하지_않는다() {
38+
Member member = memberGenerator.generate("123", "nickname");
39+
String newNickname = "nickname";
40+
41+
assertThatCode(() -> memberService.validateNickname(newNickname, member.getId()))
42+
.doesNotThrowAnyException();
43+
}
44+
45+
@Test
46+
void 중복된_닉네임이_있으면_예외가_발생한다() {
47+
memberGenerator.generate("123", "duplicate-nickname");
48+
Member member = memberGenerator.generate("456", "another-nickname");
49+
String newNickname = "duplicate-nickname";
50+
51+
BusinessException exception = assertThrows(BusinessException.class,
52+
() -> memberService.validateNickname(newNickname, member.getId()));
53+
54+
assertThat(exception.getErrorCode()).isEqualTo(BusinessErrorCode.DUPLICATE_NICKNAME);
55+
}
56+
}
57+
58+
@Nested
59+
class ValidatePhoneNumber {
60+
61+
@Test
62+
void 중복되지_않은_전화번호이면_예외가_발생하지_않는다() {
63+
memberGenerator.generate("123", "nickname");
64+
Member member = memberGenerator.generate("456", "unique-nickname");
65+
String newPhoneNumber = "01012345678";
66+
67+
assertThatCode(() -> memberService.validatePhoneNumber(newPhoneNumber, member.getId()))
68+
.doesNotThrowAnyException();
69+
}
70+
71+
@Test
72+
void 자신의_기존_전화번호이면_예외가_발생하지_않는다() {
73+
Member member = memberGenerator.generateRegisteredMember("123", "nickname", "01012345678");
74+
String newPhoneNumber = "01012345678";
75+
76+
assertThatCode(() -> memberService.validatePhoneNumber(newPhoneNumber, member.getId()))
77+
.doesNotThrowAnyException();
78+
}
79+
80+
@Test
81+
void 중복된_전화번호가_있으면_예외가_발생한다() {
82+
memberGenerator.generateRegisteredMember("123", "nickname1", "01012345678");
83+
Member member = memberGenerator.generateRegisteredMember("456", "nickname2", "01087654321");
84+
String newPhoneNumber = "01012345678";
85+
86+
BusinessException exception = assertThrows(BusinessException.class,
87+
() -> memberService.validatePhoneNumber(newPhoneNumber, member.getId()));
88+
89+
assertThat(exception.getErrorCode()).isEqualTo(BusinessErrorCode.DUPLICATE_PHONE_NUMBER);
90+
}
91+
}
92+
2293
@Nested
2394
class Update {
2495

@@ -66,9 +137,7 @@ class Update {
66137
@Test
67138
void 중복된_전화번호가_있으면_예외가_발생한다() {
68139
String phoneNumber = "01012345678";
69-
Member existMember = memberGenerator.generate("123", "nickname1");
70-
memberService.update(existMember.getId(),
71-
new MemberUpdateRequest("nickname1", phoneNumber, "성북구", true));
140+
memberGenerator.generateRegisteredMember("123", "nickname1", phoneNumber);
72141
Member updatedMember = memberGenerator.generate("456", "nickname2");
73142
MemberUpdateRequest request =
74143
new MemberUpdateRequest("new-nickname", phoneNumber, "성북구", true);
@@ -82,9 +151,7 @@ class Update {
82151
@Test
83152
void 기존의_전화번호와_동일하면_정상적으로_회원_정보가_수정된다() {
84153
String phoneNumber = "01012345678";
85-
Member member = memberGenerator.generate("123", "nickname1");
86-
memberService.update(member.getId(),
87-
new MemberUpdateRequest("nickname1", phoneNumber, "성북구", true));
154+
Member member = memberGenerator.generateRegisteredMember("123", "nickname1", phoneNumber);
88155
MemberUpdateRequest request =
89156
new MemberUpdateRequest("new-nickname", phoneNumber, "성북구", true);
90157

0 commit comments

Comments
 (0)