토이 프로젝트 스터디 #3
- 스터디 진행 날짜 : 6/2
- 스터디 작업 날짜 : 5/31 ~ 6/2
토이 프로젝트 진행 사항
- 회원 이메일 인증 로직 수정
- 기존
RDB
에 저장하던 방식을 Redis
로 변경
- 테스트 코드 작성
내용
회원 이메일 인증 로직
- 기존 로직의 경우
회원 가입 -> RDB 저장(인증 전) -> 이메일 인증 -> 인증 전 데이터를 인증 후로 변경
의 순서로 동작
- 팀원이 인증 전 회원의 데이터를
RDB
에 저장하는 것이 아니라 Redis
에 저장하는 것이 어떻겠냐는 의견 제시
- 이를 토대로
quartz
를 사용해 인증 전 회원을 삭제하는 것은 그대로 유지하고, 저장하는 주체만 RDB
에서 Redis
로 수정
public interface AuthUtilsHelper {
void saveAccountWithUUID(Account account, String uuid);
Optional<Map<String, Object>> getAccount(String email);
...
}
- 기존 토큰을
Redis
에 저장하는 역할을 하는 AuthUtilsHelper
에 추가적으로 인증 전 회원 정보와 인증 키 uuid
를 저장하는 로직 추가
private void validateSignInInfo(SignIn signIn, Account account) {
if (account.getVerify().equals(VerifyStatus.BEFORE)) {
throw new AccountNotValidateException();
}
if (account.isDeleted()) {
throw new AccountWithdrawalException();
}
if (!passwordEncoder.matches(signIn.getPassword(), account.getPassword())) {
throw new SignInFailureException();
}
}
- 인증 전 회원의 정보를
RDB
에 저장하지 않고 Redis
에 저장했기 때문에 RDB
에 없는 회원이라면 인증되지 않은 회원
- 따로
Account
의 속성을 가져와 인증된 회원인지 검증할 필요가 없어짐
validateSignInInfo()
메소드에서 인증된 회원인지 검증하는 로직 삭제
ObjectMapper LinkedHashMap
Map<String, Object> saveMap = new HashMap<>();
saveMap.put("account", account);
saveMap.put("uuid", uuid);
ObjectMapper
를 통해 Redis
에 String
형식으로 저장하고자 함
@Override
public Optional<Map<String, Object>> getAccountMap(String email) {
try {
return objectMapper.readValue((String) redisTemplate.opsForValue().get(email), Map.class);
} catch (Exception e) {
return Optional.empty();
}
}
public Optional<Account> getAccount(String email) {
return authUtilsHelper.getAccount(email)
.map(map -> Optional.ofNullable((Account) map.get("account")))
.orElse(Optional.empty());
}
java.lang.ClassCastException:
java.util.LinkedHashMap cannot be cast to com.project.board.application.account.domain.Account
Redis
에서 저장한 Map
을 꺼낸 뒤 그 Map
에서 Account
을 꺼내려고 할 때 ClassCastException
예외 발생
변환 전 타입 | 변환 후 타입 |
---|
Object | LinkedHashMap<String, Object> |
Array | ArrayList<Object> |
String | String |
number (no fraction) | Integer, Long or BigInteger (smallest applicable) |
number (fraction) | Double (configurable to use BigDecimal) |
boolean | Boolean |
null | null |
ObjectMapper
에서 Object
를 변환 시 LinkedHashMap<String, Object>
으로 변환되기 때문
@Override
public Optional<Map<String, Object>> getAccountMap(String email) {
Map<String, Object> accountMap = new HashMap<>();
try {
Map<String, Object> redisMap =
objectMapper.readValue((String) redisTemplate.opsForValue().get(email), Map.class);
accountMap.put("uuid", redisMap.get("uuid"));
accountMap.put("account", objectMapper.convertValue(redisMap.get("account"), Account.class));
return Optional.ofNullable(accountMap);
} catch (Exception e) {
return Optional.empty();
}
}
public Optional<Account> getAccount(String email) {
return authUtilsHelper.getAccountMap(email)
.map(map -> Optional.ofNullable((Account) map.get("account")))
.orElse(Optional.empty());
}
ObjectMapper.convertValue()
를 통해 LinkedHashMap
타입을 다른 타입으로 변환 가능
- 이미
ObjectMapper
를 DI
받는 AuthRedisUtilsHelper
에서 형변환을 해서 Map
으로 반환
jacoco 도입
jacoco
를 통해 테스트 커버리지를 확인하면서 테스크 코드 작성
@Test
void signUp_email_중복_실패_테스트() {
SignUp signUp = SignFactory.createSignUp("중복 이메일", "nickname");
given(accountRepository.existsByEmail(anyString())).willThrow(new AccountEmailAlreadyExistsException());
assertThrows(AccountEmailAlreadyExistsException.class, () -> accountService.signUp(signUp));
}
@Test
void signUp_nickname_중복_실패_테스트() {
SignUp signUp = SignFactory.createSignUp("test@email.com", "중복 닉네임");
given(accountRepository.existsByEmail(anyString())).willReturn(false);
given(accountRepository.existsByNickname(anyString())).willThrow(AccountNicknameAlreadyExistsException.class);
assertThrows(AccountNicknameAlreadyExistsException.class, () -> accountService.signUp(signUp));
}
- 기존에 테스트를 작성할 때에는 직접 예외를 발생하도록 함
jacoco
를 확인해보니 의도한대로 테스트가 동작하지 않았음
- 직접 예외를 발생시키는 것이 아닌, 작성한 분기문에 따라 예외가 발생하도록 작성했어야 함
@Test
void signUp_nickname_중복_실패_테스트() {
SignUp signUp = SignFactory.createSignUp("test@email.com", "중복 닉네임");
given(accountRepository.existsByEmail(anyString())).willReturn(false);
given(accountRepository.existsByNickname(anyString())).willReturn(true);
assertThrows(AccountNicknameAlreadyExistsException.class, () -> accountService.signUp(signUp));
}
@Test
void signUp_authUtils_이메일_중복_실패_테스트() {
SignUp signUp = SignFactory.createSignUp("authUtils 중복 이메일", "nickname");
given(accountRepository.existsByEmail(anyString())).willReturn(false);
given(authUtils.existsAccountByEmail(anyString())).willReturn(true);
assertThrows(AccountEmailAlreadyExistsException.class, () -> accountService.signUp(signUp));
}
- 테스트 시 직접 예외를 발생시키는 것이 아닌 제대로 분기문이 실행되도록 수정