실제 계정 메일이 아니면 실제 유저 수를 확인할 수 없다. 알림 이메일 전송 마찬가지이다. 의사소통불가의 문제
가장 먼저 무엇을 해볼 것이라면 이메일에 해당하는 유저가 있는지 확인해 볼 것이다. 지난번에 만들어놓은 레파지토리 활용한다. 일단 여기서는 repository 를 도메인 계층을 보려고 한다. 다른 패키지이긴 하지만 Account와 같은 레벨로 볼 것이다. Account를 참조하듯이 AccountRepository도 여러곳에서 참조해도 괜찮다고 볼 것! 😗😗 대신에 service 와 controller 를 Reepository 나 domain Entity에서 참조하진 않을 것이다.
AccountController.java
@GetMapping("/check-email-token")
public String checkEmailToken(String token, String email, Model model) {
Account account = accountRepository.findByEmail(email);
// 이메일이 정확하지 않은 경우에 대한 에러 처리
if (account == null) {
model.addAttribute("error", "wrong email"); // 에러 메시지, 사실은 이 메시지를 실제적으로 뿌리진 않으므로 아무거나 줘도 무방
return "account/checkedEmail";
}
// 토큰이 정확하지 않은 경웨 대한 에러 처리
if (!account.getEmailCheckToken().equals(token)) {
model.addAttribute("error", "wrong token");
return "account/checkedEmail";
}
//이메일과 토큰이 정확한 경우 가입 완료 처리
account.setEmailVerified(true);
account.setJoinedAt(LocalDateTime.now());
model.addAttribute("numberOfUser", accountRepository.count()); // 몇번째 유저이냐
model.addAttribute("nickname", account.getNickname()); // 이름 표출
return "account/checkedEmail";
}
@GetMapping("/check-email-token")
public String checkEmailToken(String token, String email, Model model) {
Account account = accountRepository.findByEmail(email);
String view = "account/checked-email";
if (account == null) {
model.addAttribute("error", "wrong.email");
return view;
}
if (!account.isValidToken(token)) {
model.addAttribute("error", "wrong.token");
return view;
}
account.completeSignUp();
accountService.login(account);
model.addAttribute("numberOfUser", accountRepository.count());
model.addAttribute("nickname", account.getNickname());
return view;
}
public void processNewAccount(SignUpForm signUpForm) {
Account newAccount = saveNewAccount(signUpForm);
newAccount.generateEmailCheckToken();
sendSignUpConfirmEmail(newAccount);
}
newAccount는 현재 detach 객체라서 db에 싱크가 되지 않은 상태이다. 따라서 emailChecktoken값이 db에 저장되지 않았다. 생성했는데 null 인 이유는?..
private Account saveNewAccount(SignUpForm signUpForm) {
Account account = Account.builder()
.email(signUpForm.getEmail())
.nickname(signUpForm.getNickname())
.password(passwordEncoder.encode(signUpForm.getPassword()))
.meetCreatedByWeb(true)
.meetEnrollmentResultByWeb(true)
.meetUpdatedByWeb(true)
.build();
Account newAccount = accountRepository.save(account);
return newAccount;
}
saveNewAccount 여기서 이미 저장을 했는데 저장을 하면 return한 객체는 accountRepository.save(account); 이 안에서만 트랜잭션이 일어난다. 즉 save 메서드 안에서만 트랜잭션 처리가 되었기 때문에 이 안에서는 해당하는 엔티티가 jpa persistence 상태이다. 나온 다음에!
public void processNewAccount(SignUpForm signUpForm) {
Account newAccount = saveNewAccount(signUpForm);
newAccount.generateEmailCheckToken();
sendSignUpConfirmEmail(newAccount);
}
여기서는 detached 상태이다. 왜? 트랜잭션 범위를 벗어났으니까! 그래서 트랜젝션 어노테이션을 붙여줘야 한다. 매우매우매우 중요! 그래서 saveNewAccount 안에서 save가 호출되더라도 나와서도 계속해서 트랜잭션 범위 안에 있기 때문에 detached 상태가 아니라 persistence 상태가 유지된다. persistence 상태의 객체는 트랜잭션이 종료될 때 상태를 db에 싱크하게 된다. 여기서 생성한 newAccount.generateEmailCheckToken() 토큰값이 이때는 저장될 것이다.
AccountControllerTest.java
@DisplayName("회원 가입 처리 - 입력값 정상")
@Test
void signUpSubmit_with_correct_input() throws Exception {
mockMvc.perform(post("/sign-up")
.param("nickname", "keesun")
.param("email", "keesun@email.com")
.param("password", "12345678")
.with(csrf()))
.andExpect(status().is3xxRedirection())
.andExpect(view().name("redirect:/"))
.andExpect(authenticated().withUsername("keesun"));
// 이메일이 있는지 확인
Account account = accountRepository.findByEmail("keesun@email.com");
assertNotNull(account);
assertNotEquals(account.getPassword(), "12345678"); //즉 평문 그대로 저장 안한다는 것이 입증됨
assertNotNull(account.getEmailCheckToken()); // 토큰이 null 이 아닌지 확인하기
// 아무런 타입 SimpleMailMessage 인스턴스를 가지고 sender가 호출되었는가만 확인 -> 메일을 보냈는지 확인하는 것
then(javaMailSender).should().send(any(SimpleMailMessage.class));
}
assertNotNull(account.getEmailCheckToken()); // 토큰이 null 이 아닌지 확인하기
출처 : 인프런 백기선님의 스프링과 JPA 기반 웹 애플리케이션 개발