Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/docs/asciidoc/api/mypage/can-change-nickname.adoc
Original file line number Diff line number Diff line change
@@ -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[]
2 changes: 2 additions & 0 deletions src/docs/asciidoc/api/mypage/mypage.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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[]
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -298,10 +298,20 @@ public SliceCustom<SubscribedCompanyResponse> 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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -154,4 +154,12 @@ public ResponseEntity<BasicResponse<Void>> changeNickname(
memberService.changeNickname(request.getNickname(), authentication);
return ResponseEntity.ok(BasicResponse.success());
}

@Operation(summary = "닉네임 변경 가능 여부 조회", description = "닉네임 변경 가능 여부를 true/false로 반환합니다.")
@GetMapping("/mypage/nickname/changeable")
public ResponseEntity<BasicResponse<Boolean>> canChangeNickname() {
Authentication authentication = AuthenticationMemberUtils.getAuthentication();
boolean result = memberService.canChangeNickname(authentication);
return ResponseEntity.ok(BasicResponse.success(result));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.*;
Expand Down Expand Up @@ -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 {
Expand Down