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
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,8 @@ public void changeNickname(String nickname, LocalDateTime now) {
this.nicknameUpdatedAt = now;
}

public boolean canChangeNickname() {
public boolean canChangeNickname(int restrictionMinutes, LocalDateTime now) {
return nicknameUpdatedAt == null
|| ChronoUnit.HOURS.between(nicknameUpdatedAt, LocalDateTime.now()) >= 24;
|| ChronoUnit.MINUTES.between(nicknameUpdatedAt, now) >= restrictionMinutes;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.dreamypatisiel.devdevdev.domain.policy;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class NicknameChangePolicy {
@Value("${nickname.change.interval.minutes:1440}")
private int nicknameChangeIntervalMinutes;

public int getNicknameChangeIntervalMinutes() {
return nicknameChangeIntervalMinutes;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.dreamypatisiel.devdevdev.domain.entity.*;
import com.dreamypatisiel.devdevdev.domain.entity.embedded.CustomSurveyAnswer;
import com.dreamypatisiel.devdevdev.domain.policy.NicknameChangePolicy;
import com.dreamypatisiel.devdevdev.domain.repository.CompanyRepository;
import com.dreamypatisiel.devdevdev.domain.repository.comment.CommentRepository;
import com.dreamypatisiel.devdevdev.domain.repository.comment.MyWrittenCommentDto;
Expand Down Expand Up @@ -47,6 +48,8 @@
import static com.dreamypatisiel.devdevdev.domain.exception.MemberExceptionMessage.MEMBER_INCOMPLETE_SURVEY_MESSAGE;
import static com.dreamypatisiel.devdevdev.domain.exception.NicknameExceptionMessage.NICKNAME_CHANGE_RATE_LIMIT_MESSAGE;

import org.springframework.beans.factory.annotation.Value;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
Expand All @@ -63,6 +66,7 @@ public class MemberService {
private final SurveyAnswerJdbcTemplateRepository surveyAnswerJdbcTemplateRepository;
private final CommentRepository commentRepository;
private final CompanyRepository companyRepository;
private final NicknameChangePolicy nicknameChangePolicy;

/**
* 회원 탈퇴 회원의 북마크와 회원 정보를 삭제합니다.
Expand Down Expand Up @@ -290,15 +294,15 @@ public SliceCustom<SubscribedCompanyResponse> findMySubscribedCompanies(Pageable
}

/**
* @Note: 유저의 닉네임을 변경합니다. 최근 24시간 이내에 변경한 이력이 있다면 닉네임 변경이 불가능합니다.
* @Note: 유저의 닉네임을 변경합니다. 설정된 제한 시간 이내에 변경한 이력이 있다면 닉네임 변경이 불가능합니다.
* @Author: 유소영
* @Since: 2025.07.03
*/
@Transactional
public String changeNickname(String nickname, Authentication authentication) {
Member member = memberProvider.getMemberByAuthentication(authentication);

if (!member.canChangeNickname()) {
if (!member.canChangeNickname(nicknameChangePolicy.getNicknameChangeIntervalMinutes(), timeProvider.getLocalDateTimeNow())) {
throw new NicknameException(NICKNAME_CHANGE_RATE_LIMIT_MESSAGE);
}

Expand All @@ -313,6 +317,6 @@ public String changeNickname(String nickname, Authentication authentication) {
*/
public boolean canChangeNickname(Authentication authentication) {
Member member = memberProvider.getMemberByAuthentication(authentication);
return member.canChangeNickname();
return member.canChangeNickname(nicknameChangePolicy.getNicknameChangeIntervalMinutes(), timeProvider.getLocalDateTimeNow());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,48 @@

class MemberTest {

@ParameterizedTest
@CsvSource({
", true", // 변경 이력 없음(null)
"60, false", // 24시간 이내
"1439, false", // 24시간 이내
"1440, true", // 24시간 경과(경계)
"1550, true", // 24시간 초과
})
@DisplayName("닉네임 변경 가능 여부 파라미터 테스트")
void canChangeNickname(Long minutesAgo, boolean expected) {
// given
LocalDateTime now = LocalDateTime.now();
Member member = new Member();
if (minutesAgo != null) {
member.changeNickname("닉네임", now.minusMinutes(minutesAgo));
}
int restrictionMinutes = 1440; // 24시간
// when
boolean result = member.canChangeNickname(restrictionMinutes, now);
// then
assertThat(result).isEqualTo(expected);
}

@ParameterizedTest
@CsvSource({
", true", // 변경 이력 없음(null)
"0, false", // 24시간 이내
"1, false", // 24시간 이내
"24, true", // 24시간 경과(경계)
"25, true", // 24시간 초과
"1, true", // 24시간 이내
"60, true", // 24시간 경과(경계)
"1440, true", // 24시간 초과
})
@DisplayName("닉네임 변경 가능 여부 파라미터 테스트")
void canChangeNickname(Long hoursAgo, boolean expected) {
void canChangeNicknameWhenDev(Long minutesAgo, boolean expected) {
// given
LocalDateTime now = LocalDateTime.now();
Member member = new Member();
if (hoursAgo != null) {
member.changeNickname("닉네임", LocalDateTime.now().minusHours(hoursAgo));
if (minutesAgo != null) {
member.changeNickname("닉네임", now.minusMinutes(minutesAgo));
}
int restrictionMinutes = 1; // 1분
// when
boolean result = member.canChangeNickname();
boolean result = member.canChangeNickname(restrictionMinutes, now);
// then
assertThat(result).isEqualTo(expected);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import com.dreamypatisiel.devdevdev.exception.NicknameException;
import com.dreamypatisiel.devdevdev.exception.SurveyException;
import com.dreamypatisiel.devdevdev.global.common.MemberProvider;
import com.dreamypatisiel.devdevdev.global.common.TimeProvider;
import com.dreamypatisiel.devdevdev.global.security.oauth2.model.SocialMemberDto;
import com.dreamypatisiel.devdevdev.global.security.oauth2.model.UserPrincipal;
import com.dreamypatisiel.devdevdev.web.dto.SliceCustom;
Expand All @@ -62,6 +63,7 @@
import org.junit.jupiter.params.provider.CsvSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.data.auditing.AuditingHandler;
import org.springframework.data.auditing.DateTimeProvider;
import org.springframework.data.domain.PageRequest;
Expand Down Expand Up @@ -119,6 +121,8 @@ class MemberServiceTest extends ElasticsearchSupportTest {
PickCommentRepository pickCommentRepository;
@Autowired
SubscriptionRepository subscriptionRepository;
@MockBean
TimeProvider timeProvider;

@Test
@DisplayName("회원이 회원탈퇴 설문조사를 완료하지 않으면 탈퇴가 불가능하다.")
Expand Down Expand Up @@ -458,6 +462,8 @@ void getBookmarkedTechArticlesNotFoundMemberException() {
@DisplayName("회원탈퇴 서베이 이력을 기록한다.")
void recordMemberExitSurveyAnswer() {
// given
when(timeProvider.getLocalDateTimeNow()).thenReturn(LocalDateTime.of(2024, 1, 1, 0, 0, 0, 0));

SocialMemberDto socialMemberDto = createSocialDto(userId, name, nickname, password, email, socialType, role);
Member member = Member.createMemberBy(socialMemberDto);
memberRepository.save(member);
Expand Down Expand Up @@ -1205,24 +1211,27 @@ void changeNickname() {
assertThat(changedNickname).isEqualTo(newNickname);
}

@DisplayName("회원이 24시간 이내에 닉네임을 변경한 적이 있다면 예외가 발생한다.")
@DisplayName("회원이 1440분(24시간) 이내에 닉네임을 변경한 적이 있다면 예외가 발생한다.")
@ParameterizedTest
@CsvSource({
"0, true",
"1, true",
"23, true",
"24, false", // 변경 허용
"25, false" // 변경 허용
"60, true", // 1시간
"1439, true", // 23.9시간
"1440, false", // 24시간, 변경 허용
"1500, false" // 25시간, 변경 허용
})
void changeNicknameThrowsExceptionWhenChangedWithin24Hours(long hoursAgo, boolean shouldThrowException) {
void changeNicknameThrowsExceptionWhenChangedWithin24Hours(long minutesAgo, boolean shouldThrowException) {
// given
LocalDateTime fixedNow = LocalDateTime.of(2024, 1, 1, 12, 0, 0);
when(timeProvider.getLocalDateTimeNow()).thenReturn(fixedNow);

String oldNickname = "이전 닉네임";
String newNickname = "새 닉네임";

SocialMemberDto socialMemberDto = createSocialDto(userId, name, oldNickname, password, email, socialType, role);
Member member = Member.createMemberBy(socialMemberDto);

member.changeNickname(oldNickname, LocalDateTime.now().minusHours(hoursAgo));
member.changeNickname(oldNickname, fixedNow.minusMinutes(minutesAgo));
memberRepository.save(member);

UserPrincipal userPrincipal = UserPrincipal.createByMember(member);
Expand All @@ -1247,20 +1256,23 @@ void changeNicknameThrowsExceptionWhenChangedWithin24Hours(long hoursAgo, boolea
@ParameterizedTest
@CsvSource({
"0, false",
"1, false",
"23, false",
"24, true",
"25, true"
"60, false", // 1시간
"1439, false", // 23.9시간
"1440, true", // 24시간
"1500, true" // 25시간
})
void canChangeNickname(long hoursAgo, boolean expected) {
void canChangeNickname(long minutesAgo, boolean expected) {
// given
LocalDateTime fixedNow = LocalDateTime.of(2024, 1, 1, 12, 0, 0);
when(timeProvider.getLocalDateTimeNow()).thenReturn(fixedNow);

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));
member.changeNickname(newNickname, fixedNow.minusMinutes(minutesAgo));
memberRepository.save(member);

UserPrincipal userPrincipal = UserPrincipal.createByMember(member);
Expand Down