44import static org .assertj .core .api .Assertions .assertThat ;
55import static org .assertj .core .api .Assertions .assertThatThrownBy ;
66import static org .mockito .ArgumentMatchers .any ;
7+ import static org .mockito .ArgumentMatchers .anyLong ;
78import static org .mockito .ArgumentMatchers .anyString ;
9+ import static org .mockito .BDDMockito .given ;
810import static org .mockito .Mockito .doNothing ;
911import static org .mockito .Mockito .times ;
1012import static org .mockito .Mockito .verify ;
1921import org .mockito .InjectMocks ;
2022import org .mockito .Mock ;
2123import org .mockito .junit .jupiter .MockitoExtension ;
24+ import org .programmers .signalbuddyfinal .domain .auth .dto .EmailRequest ;
2225import org .programmers .signalbuddyfinal .domain .auth .dto .LoginRequest ;
2326import org .programmers .signalbuddyfinal .domain .auth .dto .LoginResponse ;
2427import org .programmers .signalbuddyfinal .domain .auth .dto .LogoutResponse ;
2528import org .programmers .signalbuddyfinal .domain .auth .dto .NewTokenResponse ;
2629import org .programmers .signalbuddyfinal .domain .auth .dto .ReissueResponse ;
2730import org .programmers .signalbuddyfinal .domain .auth .dto .SocialLoginRequest ;
31+ import org .programmers .signalbuddyfinal .domain .auth .dto .VerifyCodeRequest ;
32+ import org .programmers .signalbuddyfinal .domain .auth .entity .Purpose ;
2833import org .programmers .signalbuddyfinal .domain .auth .exception .AuthErrorCode ;
2934import org .programmers .signalbuddyfinal .domain .member .entity .Member ;
3035import org .programmers .signalbuddyfinal .domain .member .entity .enums .MemberRole ;
3843import org .programmers .signalbuddyfinal .global .security .basic .CustomUserDetails ;
3944import org .programmers .signalbuddyfinal .global .security .jwt .JwtService ;
4045import org .programmers .signalbuddyfinal .global .security .jwt .JwtUtil ;
46+ import org .springframework .data .redis .core .RedisTemplate ;
47+ import org .springframework .data .redis .core .ValueOperations ;
4148import org .springframework .security .authentication .AuthenticationManager ;
4249import org .springframework .security .authentication .UsernamePasswordAuthenticationToken ;
4350import org .springframework .security .core .Authentication ;
@@ -63,6 +70,15 @@ class AuthServiceTest {
6370 @ Mock
6471 private JwtUtil jwtUtil ;
6572
73+ @ Mock
74+ EmailService emailService ;
75+
76+ @ Mock
77+ RedisTemplate <String , String > redisTemplate ;
78+
79+ @ Mock
80+ ValueOperations <String , String > valueOperations ;
81+
6682 private Member member ;
6783 private String deviceTokenCookie = "deviceToken" ;
6884 private CustomUserDetails customUserDetails ;
@@ -106,7 +122,8 @@ void givenWithdrawalMember_whenBasicLogin_thenReturnWithdrawnMemberMessage() {
106122 // given
107123 Member withdrawalMember = createMember (MemberStatus .WITHDRAWAL );
108124
109- LoginRequest loginRequest = new LoginRequest (withdrawalMember .getEmail (), withdrawalMember .getPassword ());
125+ LoginRequest loginRequest = new LoginRequest (withdrawalMember .getEmail (),
126+ withdrawalMember .getPassword ());
110127 when (authenticationManager .authenticate (any ())).thenThrow (
111128 new BusinessException (MemberErrorCode .WITHDRAWN_MEMBER ));
112129
@@ -310,7 +327,111 @@ void givenValidToken_whenLogout_thenSuccess() {
310327 assertThat (afterLogoutSetCookieHeader ).contains ("Max-Age=0" );
311328 }
312329
313- private Member createMember (MemberStatus status ){
330+ @ Nested
331+ @ DisplayName ("이메일 검증" )
332+ class whenVerifyEmail {
333+
334+ @ Test
335+ @ DisplayName ("이메일이 존재하는 경우, EmailService의 sendEmail을 호출한다." )
336+ void givenExistedEmail_whenEmailVerification_thenCallEmailService () {
337+ //given
338+ when (memberRepository .findByEmail (member .getEmail ())).thenReturn (Optional .of (member ));
339+ doNothing ().when (emailService ).sendEmail (member .getEmail ());
340+
341+ //when
342+ authService .emailVerification (new EmailRequest (member .getEmail ()));
343+
344+ //then
345+ verify (emailService , times (1 )).sendEmail (member .getEmail ());
346+ }
347+
348+ @ Test
349+ @ DisplayName ("이메일이 존재하지 않는 경우, NotFoundMember를 던진다." )
350+ void givenNotExistedMember_whenEmailVerification_thenThrowsNotFoundError () {
351+ //given
352+ when (memberRepository .findByEmail (member .getEmail ())).thenReturn (Optional .empty ());
353+
354+ //when
355+ assertThatThrownBy (
356+ () -> authService .emailVerification (new EmailRequest (member .getEmail ())))
357+ .isInstanceOf (BusinessException .class )
358+ .hasMessageContaining (MemberErrorCode .NOT_FOUND_MEMBER .getMessage ());
359+
360+ verify (emailService , times (0 )).sendEmail (member .getEmail ());
361+ }
362+
363+ @ Test
364+ @ DisplayName ("탈퇴한 회원인 경우, NotFoundMember를 던진다." )
365+ void givenWithdrawalMember_whenEmailVerification_thenThrowsNotFoundError () {
366+ //given
367+ Member withdrawalMember = createMember (MemberStatus .WITHDRAWAL );
368+ when (memberRepository .findByEmail (withdrawalMember .getEmail ())).thenReturn (
369+ Optional .of (withdrawalMember ));
370+
371+ //when
372+ assertThatThrownBy (
373+ () -> authService .emailVerification (new EmailRequest (withdrawalMember .getEmail ())))
374+ .isInstanceOf (BusinessException .class )
375+ .hasMessageContaining (MemberErrorCode .NOT_FOUND_MEMBER .getMessage ());
376+
377+ verify (emailService , times (0 )).sendEmail (member .getEmail ());
378+ }
379+ }
380+
381+ @ Nested
382+ @ DisplayName ("인증 코드 검증" )
383+ class whenVerifyCode {
384+
385+ VerifyCodeRequest verifyCodeRequest = new VerifyCodeRequest (Purpose .NEW_PASSWORD ,
386+ "test@test.com" , "123456" );
387+
388+ @ BeforeEach
389+ void verifyCodeSetup () {
390+ given (redisTemplate .opsForValue ()).willReturn (valueOperations );
391+ }
392+
393+ @ Test
394+ @ DisplayName ("인증에 성공한다." )
395+ void givenValidCode_whenVerifyCode_thenReturnVoid () {
396+ // given
397+ when (valueOperations .get (any ())).thenReturn (verifyCodeRequest .getCode ());
398+ when (redisTemplate .delete (anyString ())).thenReturn (true );
399+ doNothing ().when (valueOperations ).set (any (), any (), any (Long .class ), any ());
400+
401+ // when
402+ authService .verifyCode (verifyCodeRequest );
403+
404+ // then
405+ verify (redisTemplate , times (1 )).delete (anyString ());
406+ verify (valueOperations , times (1 )).set (anyString (), anyString (), anyLong (), any ());
407+ }
408+
409+ @ Test
410+ @ DisplayName ("인증 유효 시간이 끝나서 INVALID_AUTH_CODE 에러를 반환한다." )
411+ void givenAuthExpirationTimeIsEnd_whenVerifyCode_thenThrowsInvalidAuthCodeError () {
412+ // given
413+ when (valueOperations .get (any ())).thenReturn (null );
414+
415+ // when & then
416+ assertThatThrownBy (() -> authService .verifyCode (verifyCodeRequest ))
417+ .isInstanceOf (BusinessException .class )
418+ .hasMessageContaining (AuthErrorCode .INVALID_AUTH_CODE .getMessage ());
419+ }
420+
421+ @ Test
422+ @ DisplayName ("인증코드가 일치하지 않아 NOT_MATCH_AUTH_CODE 에러를 반환한다." )
423+ void givenNotMatchedAuthCode_whenVerifyCode_thenThrowsNotMatchAuthCodeError () {
424+ // given
425+ when (valueOperations .get (any ())).thenReturn ("wrong" );
426+
427+ // when & then
428+ assertThatThrownBy (() -> authService .verifyCode (verifyCodeRequest ))
429+ .isInstanceOf (BusinessException .class )
430+ .hasMessageContaining (AuthErrorCode .NOT_MATCH_AUTH_CODE .getMessage ());
431+ }
432+ }
433+
434+ private Member createMember (MemberStatus status ) {
314435 return Member .builder ()
315436 .email ("test@test.com" )
316437 .nickname ("테스트" )
0 commit comments