From c08408047d66bf26e8f874a3e3caf984b98604ad Mon Sep 17 00:00:00 2001 From: soyoung Date: Sun, 6 Jul 2025 16:01:24 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat(nickname):=20=EB=8B=89=EB=84=A4?= =?UTF-8?q?=EC=9E=84=20=EB=B3=80=EA=B2=BD=20=EA=B0=80=EB=8A=A5=20=EC=97=AC?= =?UTF-8?q?=EB=B6=80=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/mypage/can-change-nickname.adoc | 19 +++++++++++ .../devdevdev/domain/entity/Member.java | 2 +- .../domain/service/member/MemberService.java | 12 ++++++- .../controller/member/MypageController.java | 8 +++++ .../devdevdev/domain/entity/MemberTest.java | 5 ++- .../service/member/MemberServiceTest.java | 33 ++++++++++++++++++ .../MyPageControllerUsedMockServiceTest.java | 21 ++++++++++++ ...PageControllerDocsUsedMockServiceTest.java | 34 +++++++++++++++++++ 8 files changed, 129 insertions(+), 5 deletions(-) create mode 100644 src/docs/asciidoc/api/mypage/can-change-nickname.adoc diff --git a/src/docs/asciidoc/api/mypage/can-change-nickname.adoc b/src/docs/asciidoc/api/mypage/can-change-nickname.adoc new file mode 100644 index 00000000..57387b1d --- /dev/null +++ b/src/docs/asciidoc/api/mypage/can-change-nickname.adoc @@ -0,0 +1,19 @@ +[[ChangeNickname]] +== 닉네임 변경 가능 여부 API(GET: /devdevdev/api/v1/mypage/nickname/changeable) +* 회원은 닉네임 변경 가능 여부를 확인할 수 있다. +* 비회원은 닉네임 변경 가능 여부를 확인할 수 없다. + +=== 정상 요청/응답 +==== HTTP Request +include::{snippets}/can-change-nickname/http-request.adoc[] +==== HTTP Request Header Fields +include::{snippets}/can-change-nickname/request-headers.adoc[] + +==== HTTP Response +include::{snippets}/can-change-nickname/http-response.adoc[] +==== HTTP Response Fields +include::{snippets}/can-change-nickname/response-fields.adoc[] + +=== 예외 +==== HTTP Response +include::{snippets}/not-found-member-exception/response-body.adoc[] \ No newline at end of file diff --git a/src/main/java/com/dreamypatisiel/devdevdev/domain/entity/Member.java b/src/main/java/com/dreamypatisiel/devdevdev/domain/entity/Member.java index beb7141a..0f7b6c26 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/domain/entity/Member.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/domain/entity/Member.java @@ -196,7 +196,7 @@ public void changeNickname(String nickname, LocalDateTime now) { this.nicknameUpdatedAt = now; } - public boolean isAvailableToChangeNickname() { + public boolean canChangeNickname() { return nicknameUpdatedAt == null || ChronoUnit.HOURS.between(nicknameUpdatedAt, LocalDateTime.now()) >= 24; } diff --git a/src/main/java/com/dreamypatisiel/devdevdev/domain/service/member/MemberService.java b/src/main/java/com/dreamypatisiel/devdevdev/domain/service/member/MemberService.java index 90e259d3..d47d2f82 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/domain/service/member/MemberService.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/domain/service/member/MemberService.java @@ -298,10 +298,20 @@ public SliceCustom findMySubscribedCompanies(Pageable public void changeNickname(String nickname, Authentication authentication) { Member member = memberProvider.getMemberByAuthentication(authentication); - if (!member.isAvailableToChangeNickname()) { + if (!member.canChangeNickname()) { throw new NicknameException(NICKNAME_CHANGE_RATE_LIMIT_MESSAGE); } member.changeNickname(nickname, timeProvider.getLocalDateTimeNow()); } + + /** + * @Note: 유저가 닉네임을 변경할 수 있는지 여부를 반환합니다. + * @Author: 유소영 + * @Since: 2025.07.06 + */ + public boolean canChangeNickname(Authentication authentication) { + Member member = memberProvider.getMemberByAuthentication(authentication); + return member.canChangeNickname(); + } } diff --git a/src/main/java/com/dreamypatisiel/devdevdev/web/controller/member/MypageController.java b/src/main/java/com/dreamypatisiel/devdevdev/web/controller/member/MypageController.java index 3708783a..3b0c6a60 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/web/controller/member/MypageController.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/web/controller/member/MypageController.java @@ -154,4 +154,12 @@ public ResponseEntity> changeNickname( memberService.changeNickname(request.getNickname(), authentication); return ResponseEntity.ok(BasicResponse.success()); } + + @Operation(summary = "닉네임 변경 가능 여부 조회", description = "닉네임 변경 가능 여부를 true/false로 반환합니다.") + @GetMapping("/mypage/nickname/changeable") + public ResponseEntity> canChangeNickname() { + Authentication authentication = AuthenticationMemberUtils.getAuthentication(); + boolean result = memberService.canChangeNickname(authentication); + return ResponseEntity.ok(BasicResponse.success(result)); + } } diff --git a/src/test/java/com/dreamypatisiel/devdevdev/domain/entity/MemberTest.java b/src/test/java/com/dreamypatisiel/devdevdev/domain/entity/MemberTest.java index 4ffe63e9..84f77efe 100644 --- a/src/test/java/com/dreamypatisiel/devdevdev/domain/entity/MemberTest.java +++ b/src/test/java/com/dreamypatisiel/devdevdev/domain/entity/MemberTest.java @@ -6,7 +6,6 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; -import org.junit.jupiter.api.Test; class MemberTest { @@ -19,14 +18,14 @@ class MemberTest { "25, true", // 24시간 초과 }) @DisplayName("닉네임 변경 가능 여부 파라미터 테스트") - void isAvailableToChangeNickname_Parameterized(Long hoursAgo, boolean expected) { + void canChangeNickname(Long hoursAgo, boolean expected) { // given Member member = new Member(); if (hoursAgo != null) { member.changeNickname("닉네임", LocalDateTime.now().minusHours(hoursAgo)); } // when - boolean result = member.isAvailableToChangeNickname(); + boolean result = member.canChangeNickname(); // then assertThat(result).isEqualTo(expected); } diff --git a/src/test/java/com/dreamypatisiel/devdevdev/domain/service/member/MemberServiceTest.java b/src/test/java/com/dreamypatisiel/devdevdev/domain/service/member/MemberServiceTest.java index c96a244b..6bbaa0cf 100644 --- a/src/test/java/com/dreamypatisiel/devdevdev/domain/service/member/MemberServiceTest.java +++ b/src/test/java/com/dreamypatisiel/devdevdev/domain/service/member/MemberServiceTest.java @@ -1242,6 +1242,39 @@ void changeNicknameThrowsExceptionWhenChangedWithin24Hours(long hoursAgo, boolea } } + @DisplayName("회원의 닉네임 변경 가능 여부를 반환한다.") + @ParameterizedTest + @CsvSource({ + "0, false", + "1, false", + "23, false", + "24, true", + "25, true" + }) + void canChangeNickname(long hoursAgo, boolean expected) { + // given + String oldNickname = "이전 닉네임"; + String newNickname = "새 닉네임"; + + SocialMemberDto socialMemberDto = createSocialDto(userId, name, oldNickname, password, email, socialType, role); + Member member = Member.createMemberBy(socialMemberDto); + + member.changeNickname(newNickname, LocalDateTime.now().minusHours(hoursAgo)); + memberRepository.save(member); + + UserPrincipal userPrincipal = UserPrincipal.createByMember(member); + SecurityContext context = SecurityContextHolder.getContext(); + context.setAuthentication(new OAuth2AuthenticationToken(userPrincipal, userPrincipal.getAuthorities(), + userPrincipal.getSocialType().name())); + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + // when + boolean result = memberService.canChangeNickname(authentication); + + // then + assertThat(result).isEqualTo(expected); + } + private static Company createCompany(String companyName, String officialUrl, String careerUrl, String imageUrl, String description, String industry) { return Company.builder() diff --git a/src/test/java/com/dreamypatisiel/devdevdev/web/controller/member/MyPageControllerUsedMockServiceTest.java b/src/test/java/com/dreamypatisiel/devdevdev/web/controller/member/MyPageControllerUsedMockServiceTest.java index 47c9d847..98922142 100644 --- a/src/test/java/com/dreamypatisiel/devdevdev/web/controller/member/MyPageControllerUsedMockServiceTest.java +++ b/src/test/java/com/dreamypatisiel/devdevdev/web/controller/member/MyPageControllerUsedMockServiceTest.java @@ -131,6 +131,27 @@ void changeNicknameThrowsExceptionWhenChangedWithin24Hours() throws Exception { verify(memberService).changeNickname(eq(newNickname), any()); } + @Test + @DisplayName("회원은 닉네임 변경 가능 여부를 확인할 수 있다.") + void canChangeNickname() throws Exception { + // given + boolean result = true; + + // when + when(memberService.canChangeNickname(any())).thenReturn(result); + + // then + mockMvc.perform(get("/devdevdev/api/v1/mypage/nickname/changeable") + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding(StandardCharsets.UTF_8) + .header(SecurityConstant.AUTHORIZATION_HEADER, SecurityConstant.BEARER_PREFIX + accessToken)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.resultType").value(SUCCESS.name())) + .andExpect(jsonPath("$.data").isNotEmpty()) + .andExpect(jsonPath("$.data").isBoolean()); + } + @Test @DisplayName("회원이 내가 썼어요 댓글을 조회한다.") void getMyWrittenComments() throws Exception { diff --git a/src/test/java/com/dreamypatisiel/devdevdev/web/docs/MyPageControllerDocsUsedMockServiceTest.java b/src/test/java/com/dreamypatisiel/devdevdev/web/docs/MyPageControllerDocsUsedMockServiceTest.java index 42e9065e..388b4c0c 100644 --- a/src/test/java/com/dreamypatisiel/devdevdev/web/docs/MyPageControllerDocsUsedMockServiceTest.java +++ b/src/test/java/com/dreamypatisiel/devdevdev/web/docs/MyPageControllerDocsUsedMockServiceTest.java @@ -6,6 +6,7 @@ import static com.dreamypatisiel.devdevdev.web.docs.format.ApiDocsFormatGenerator.myWrittenCommentSort; import static com.dreamypatisiel.devdevdev.web.docs.format.ApiDocsFormatGenerator.stringOrNull; import static com.dreamypatisiel.devdevdev.web.docs.format.ApiDocsFormatGenerator.uniqueCommentIdType; +import static com.dreamypatisiel.devdevdev.web.dto.response.ResultType.SUCCESS; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; @@ -168,6 +169,39 @@ void changeNicknameThrowsExceptionWhenChangedWithin24Hours() throws Exception { )); } + @Test + @DisplayName("회원은 닉네임 변경 가능 여부를 확인할 수 있다.") + void canChangeNickname() throws Exception { + // given + boolean result = true; + + // when + when(memberService.canChangeNickname(any())).thenReturn(result); + + // then + mockMvc.perform(get("/devdevdev/api/v1/mypage/nickname/changeable") + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding(StandardCharsets.UTF_8) + .header(SecurityConstant.AUTHORIZATION_HEADER, SecurityConstant.BEARER_PREFIX + accessToken)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.resultType").value(SUCCESS.name())) + .andExpect(jsonPath("$.data").isNotEmpty()) + .andExpect(jsonPath("$.data").isBoolean()) + + .andDo(document("can-change-nickname", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestHeaders( + headerWithName(AUTHORIZATION_HEADER).description("Bearer 엑세스 토큰") + ), + responseFields( + fieldWithPath("resultType").type(JsonFieldType.STRING).description("응답 결과"), + fieldWithPath("data").type(JsonFieldType.BOOLEAN).description("응답 데이터(변경 가능 여부)") + ) + )); + } + @Test @DisplayName("회원이 내가 썼어요 댓글을 조회한다.") void getMyWrittenComments() throws Exception { From e94282c2641d2be7d2b9b9af7700039214b14b27 Mon Sep 17 00:00:00 2001 From: soyoung Date: Sun, 6 Jul 2025 16:05:53 +0900 Subject: [PATCH 2/2] =?UTF-8?q?docs(nickname):=20RestDocs=20=EB=88=84?= =?UTF-8?q?=EB=9D=BD=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/api/mypage/can-change-nickname.adoc | 2 +- src/docs/asciidoc/api/mypage/mypage.adoc | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/docs/asciidoc/api/mypage/can-change-nickname.adoc b/src/docs/asciidoc/api/mypage/can-change-nickname.adoc index 57387b1d..c2810804 100644 --- a/src/docs/asciidoc/api/mypage/can-change-nickname.adoc +++ b/src/docs/asciidoc/api/mypage/can-change-nickname.adoc @@ -1,4 +1,4 @@ -[[ChangeNickname]] +[[CanChangeNickname]] == 닉네임 변경 가능 여부 API(GET: /devdevdev/api/v1/mypage/nickname/changeable) * 회원은 닉네임 변경 가능 여부를 확인할 수 있다. * 비회원은 닉네임 변경 가능 여부를 확인할 수 없다. diff --git a/src/docs/asciidoc/api/mypage/mypage.adoc b/src/docs/asciidoc/api/mypage/mypage.adoc index a8fb562a..5dfd81ca 100644 --- a/src/docs/asciidoc/api/mypage/mypage.adoc +++ b/src/docs/asciidoc/api/mypage/mypage.adoc @@ -8,3 +8,5 @@ include::record-exit-survey.adoc[] include::comment-get.adoc[] include::subscribed-companies.adoc[] include::random-nickname.adoc[] +include::change-nickname.adoc[] +include::can-change-nickname.adoc[]