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..c2810804 --- /dev/null +++ b/src/docs/asciidoc/api/mypage/can-change-nickname.adoc @@ -0,0 +1,19 @@ +[[CanChangeNickname]] +== 닉네임 변경 가능 여부 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/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[] 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 {